I have create a TokenService
class with the purpose of returning an access token. The getToken
function checks if the token has expired and if true, it will try fetching a new accessToken given the refreshToken. However, it is important that only one request goes out attempting to refresh the token. Therefore, if the function gets called in parallel, it needs to wait until the first call has completed and return that result for all other calls.
This is my current implementation:
class TokenService {
private isRefreshing: boolean = false
public getToken = async (): Promise<string> => {
const accessToken = window.localStorage.getItem('accessToken')
if (!accessToken) return Promise.resolve(null)
// Check if token has expired
const { exp } = JSON.parse(atob(accessToken.split('.')[1]))
if (new Date() < new Date(exp * 1e3))
return Promise.resolve(accessToken)
if (this.isRefreshing) {
// Wait for request to finish and return result
}
// Refresh token
this.isRefreshing = true
const refreshedAccessToken = await this.refreshToken()
return Promise.resolve(refreshedAccessToken)
}
}
I have added a flag isRefreshing
indicating if there is currently a refresh request in progress. I am unsure how to go from here. How do I wait for the request to finish and return its result?
CodePudding user response:
You must reference isRefreshing
by going through this
first - it's not a standalone identifier.
When refreshing the token, assign the result of the Promise to a property on the class. This will let further calls of getToken
to return that Promise if it exists. If it doesn't exist, and if the current token has expired, generate another.
Because you sometimes do Promise.resolve(null)
, you sometimes return null
, not a string, so you should change the type signature of getToken
appropriately.
class TokenService {
private tokenPromise: null | Promise<string> = null;
public getToken = async (): Promise<string | null> => {
const accessToken = window.localStorage.getItem('accessToken')
if (!accessToken) return null;
// Check if token has expired
const { exp } = JSON.parse(atob(accessToken.split('.')[1]))
if (new Date() <= new Date(exp * 1e3)) {
// no need to retrieve another
return accessToken;
}
if (this.tokenPromise) return this.tokenPromise;
this.tokenPromise = this.refreshToken()
.catch((error) => {
window.localStorage.removeItem('accessToken');
return null;
})
.finally(() => {
this.tokenPromise = null;
});
return this.tokenPromise;
}
}