I have some code that needs to access an API that requires OAuth authorization in the form of a token, and every time the token expires, it needs to be refreshed.
I made a function called _fetch()
that wraps the API calls that I'm making, and will both authorize the requests, and automatically refresh the token if it has expired.
The caveat is that if I call some function that rapidly calls _fetch()
\$N\$ amount of times when the token has expired, then reAuth()
, which refreshes the token, will also be called \$N\$ amount of times, which is undesirable due to the fact that it'll make \$N\$ API calls to reauthorize the token when only 1 is required.
Reduced to a really small example, possibly with mistakes, the code that I have looks a little like this
import fetch from 'node-fetch' class Example { async reAuth() { // refresh the token return fetch('authAPI') } async doMultipleAsyncThings() { return await Promise.all([ _fetch('url1'), _fetch('url2'), _fetch('url3') ]) } async _fetch(url, userOptions) { if (tokenIsExpired(this.token)) { this.token = await this.reAuth(); } const options = { headers: { Authorization: `Bearer ${this.token}` }, ...userOptions } return fetch(url, options); } } const e = new Example() e.doMultipleAsyncThings() // reAuth called 3 times if token expired
I think I have a solution to the problem, but I'm not sure if it's overkill or not.
Basically, I would memoize the reAuth()
function such that as long as the promise it returns the first time isn't resolved it would return that same promise for all future calls to prevent calling the OAuth API again and again. Then when the promise resolves it would delete itself from the internal cache so that the next time reAuth()
is called it would once again actually call the API.
I implemented this here: https://github.com/AriaFallah/weak-memoize/blob/master/src/index.js
// @flow
function memoize(callback: Function): Function {
const cache = {}
function memoized(...parameters: Array<mixed>) {
const cacheKey = JSON.stringify(parameters)
if (cache[cacheKey]) {
return cache[cacheKey]
}
// Get and add the value to the cache
const value = callback.apply(this, parameters)
cache[cacheKey] = value
if (!value || value.constructor.name !== 'Promise') {
throw new Error('Memoization Error, Async function returned non-promise value')
}
// Delete the value regardless of whether it resolves or rejects
return value.then(function(internalValue) {
cache[cacheKey] = false
return internalValue
}, function(err) {
cache[cacheKey] = false
throw err
})
}
memoized.cache = cache
return memoized
}
export default memoize
and then to solve the problem all I would need to do is decorate reAuth()
import memoize from 'weak-memoize/promise'
Example.reAuth = memoize(Example.reAuth)
I think the above makes sense, but I'm curious if there is a simpler way to fix the problem I'm facing.