Похоже, вы бы выиграли от маленького одноэлементного объекта, который управляет токеном для вас. Вы можете создать интерфейс для получения токена, который выполняет следующие действия:
- Если в кэше нет соответствующего токена, go получает новый токен и возвращает обещание, которое будет разрешено с токеном. Храните это обещание в кэше вместо токена.
- Если в кэше есть соответствующий токен, проверьте срок его действия. Если срок его действия истек или он истекает, удалите его и go к шагу 1. Если это все еще хорошо, верните обещание, которое разрешается с помощью кэшированного токена (таким образом, оно всегда возвращает обещание, независимо от того, кэшировано оно или нет).
- Если кэш находится в процессе получения нового токена, в кэше будет храниться токен fre sh, который представляет будущее поступление нового токена, поэтому кэш может просто вернуть это обещание и преобразуется в токен, который находится в процессе выборки.
Код вызывающего абонента будет выглядеть следующим образом:
tokenCache.getToken().then(token => {
// use token here
});
Все логики c за шагами 1, 2 и 3 инкапсулированы в методе getToken()
.
Вот схема для класса tokenCache
, которая, надеюсь, даст вам общее представление:
const tokenExpiration = 60 * 60 * 1000; // 1 hr in ms
const tokenBeforeTime = 5 * 60 * 1000; // 5 min in ms
class tokenCache {
constructor() {
this.tokenPromise = null;
this.timer = null;
// go get the first token
this._getNewToken().catch(err => {
console.log("error fetching initial token", err);
});
}
getToken() {
if (this.tokenPromise) {
return this.tokenPromise().then(tokenData => {
// if token has expired
if (tokenData.expires < Date.now()) {
return this._getNewToken();
} else {
return tokenData.token;
}
});
} else {
return this._getNewToken();
}
}
// non-public method for getting a new token
_getNewToken() {
// for example purposes, this uses the got() library to make an http request
// you fill in however you want to contact the identity provider to get a new token
this.tokenPromise = got(tokenURL).then(token => {
// make resolve value be an object that contains the token and the expiration
// set timer to get a new token automatically right before expiration
this._scheduleTokenRefresh(tokenExpiration - tokenBeforeTime);
return {
token: token,
expires: Date.now() + tokenExpiration;
}
}).catch(err => {
// up error, clear the cached promise, log the error, keep the promise rejected
console.log(err);
this.tokenPromise = null;
throw err;
});
return this.tokenPromise;
}
// schedule a call to refresh the token before it expires
_scheduleTokenRefresh(t) {
if (this.timer) {
clearTimeout(this.timer);
}
this.timer = setTimeout(() => {
this._getNewToken().catch(err => {
console.log("Error updating token before expiration", err);
});
this.timer = null;
}, t);
}
}
Как мы обрабатываем состояние гонки, когда существует параллельный запрос к сервису A, который может привести к тому, что несколько запросов отправляются для получения токена доступа и для нескольких обновлений в кэш?
Вы храните выпускной и всегда возвращай это обещание. Если вы хотите получить новый токен или он уже есть в этом обещании, это не имеет значения. Вы возвращаете обещание, и вызывающий абонент использует .then()
или await
для получения токена. Это «просто работает» в любом случае.
Допустим, токен доступа имеет 1 час истечения. Как у нас есть механизм для получения нового токена до его истечения?
Вы можете проверить токен на срок действия, когда он запрашивается, и если срок его действия истек, вы заменяете существующее обещание на то, которое представляет новый запрос на токен.