Last active
September 12, 2022 17:03
-
-
Save apechkin/9ea6b41df7366bf4249c22c3a2afa451 to your computer and use it in GitHub Desktop.
Механизм обновления access токена, если одновременно пришло более одного запроса с ошибкой Token Error
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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