7
\$\begingroup\$

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.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

What about modifying the return of _fetch()? Inside the if statement that checks you could have it return false or a custom error code at that point. And then back one scope, catch the return of _fetch() and test it against your error code or false then just handle it appropriately. Either escaping out with return or reauthorizing in between.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.