Skip to content

Instantly share code, notes, and snippets.

@apechkin
Last active September 12, 2022 17:03
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save apechkin/9ea6b41df7366bf4249c22c3a2afa451 to your computer and use it in GitHub Desktop.
Save apechkin/9ea6b41df7366bf4249c22c3a2afa451 to your computer and use it in GitHub Desktop.
Механизм обновления access токена, если одновременно пришло более одного запроса с ошибкой Token Error
protected onResponseSuccess = async <T>(response: AxiosResponse): Promise<T> => {
const config = response?.config || {}
if (this.subject.getState() === 'pending') {
let observer = null
await new Promise<void>((resolve) => {
observer = new Observer(resolve)
this.subject.attach(observer)
})
this.subject.detach(observer)
const configWithToken = this.updateConfigToken(config)
return this.instance(configWithToken)
}
try {
this.subject.setState('pending')
const token = await refreshAccessToken()
if (token) {
this.accessToken = token
dispatchAction(renewAccessToken(this.accessToken))
this.subject.setState('ready')
const configWithToken = this.updateConfigToken(config)
return this.instance(configWithToken)
}
this.subject.setState('ready')
dispatchAction(this.unauthorizedErrors[this.observeNode](data))
return data
} catch (err) {
this.subject.setState('ready')
// логаут из системы, что-то пошло не так
dispatchAction(this.unauthorizedErrors[this.observeNode](data))
return data
}
}
import { ISubject, Subject } from '#src/modules/api/.../Subject'
/**
* Интерфейс Наблюдателя объявляет метод уведомления, который издатели
* используют для оповещения своих подписчиков.
*/
export interface IObserver {
// Получить обновление от субъекта.
update(subject: ISubject): void
}
type TResolve = (value: void | PromiseLike<void>) => void
export class Observer implements IObserver {
private readonly resolve: TResolve
/**
* Инициация инстанса наблюдателя
* @param resolve функция объекта Promise, а именно Promise.resolve
*/
public constructor(resolve: TResolve) {
this.resolve = resolve
}
/**
* Резолвим промис если состояние объекта изменится на ready
* @param subject - объект в котором происходят изменения состояния
*/
public update(subject: ISubject): void {
if (subject instanceof Subject && subject.getState() === 'ready') this.resolve()
}
}
import { IObserver } from '#src/modules/api/.../Observer'
/**
* Интферфейс издателя объявляет набор методов для управлениями подписчиками.
*/
export interface ISubject {
// Присоединяет наблюдателя к издателю.
attach(observer: IObserver): void
// Отсоединяет наблюдателя от издателя.
detach(observer: IObserver): void
// Уведомляет всех наблюдателей о событии.
notify(): void
}
type TStates = 'ready' | 'pending'
/**
* Издатель владеет некоторым важным состоянием и оповещает наблюдателей о его
* изменениях.
*/
export class Subject implements ISubject {
/**
* @type {number} Для удобства в этой переменной хранится состояние
* Издателя, необходимое всем подписчикам.
*/
private _state: TStates = 'ready'
/**
* @type {IObserver[]} Список подписчиков. В реальной жизни список
* подписчиков может храниться в более подробном виде (классифицируется по
* типу события и т.д.)
*/
private readonly observers: IObserver[] = []
/**
* Методы управления подпиской.
*/
public attach(observer: IObserver): void {
const isExist = this.observers.includes(observer)
if (isExist) return
this.observers.push(observer)
}
public detach(observer: IObserver | null): void {
if (observer === null) return
const observerIndex = this.observers.indexOf(observer)
if (observerIndex === -1) return
this.observers.splice(observerIndex, 1)
}
/**
* Запуск обновления в каждом подписчике.
*/
public notify(): void {
for (const observer of this.observers) observer.update(this)
}
/**
* Установка нового состояния
* @param state
*/
public setState(state: TStates): void {
this._state = state
this.notify()
}
/**
* Получение текущего состояния
*/
public getState(): TStates {
return this._state
}
}
private updateConfigToken(config: AxiosRequestConfig): AxiosRequestConfig {
// замена токена в исходном запросе FormData
const requestData = config.data as FormData
if (isFormData(requestData)) {
requestData.set('token', this.accessToken)
config.data = requestData
}
if (typeof config.params === 'object') {
// замена токена в исходном запросе при переходе по ссылке требующей токен
config.params = { ...config.params, token: this.accessToken } as TRequestParams
}
return config
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment