How to use asynchronous error handlers in onError callback?

sometimes the onError callback from @apollo/client/link/error needs to be an async function, for example, to access the AsyncStorage from React Native, that is my case. So there is a way to pass an async callback to onError?

1 Like

@crisf.codes can you share some example code that uses AsyncStorage and shows when the onError callback needs to be async?

@hwillson for example there are two cases in which I would like to use asyncstorage, either there is a 401 response and I want to remove the tokens from storage or I want to refresh the token from storage and I have to fetch another token from the server, that is an async operation.

Remove tokens from storage with 401 response from server

import {setContext} from '@apollo/client/link/context';
import {ErrorResponse, onError} from '@apollo/client/link/error';

import {isJwtError, JWTError} from './errors';
import {getAuthToken, removeTokens} from './utils';

interface ResponseError extends ErrorResponse {
  networkError?: Error & {
    statusCode?: number;
    bodyText?: string;
  };
}

export const invalidateTokenLink = onError((error: ResponseError) => {
  if (
    (error.networkError && error.networkError.statusCode === 401) ||
    error.graphQLErrors?.some(isJwtError)
  ) {
    if (error.graphQLErrors[0].extensions.code !== JWTError.expired) {
      // this process is async, since it uses asyncstorage to remove the tokens. but imagine I want to 
      // refresh the tokens, it is still an asynchronous process.
      removeTokens();
    }
  }
});

Got it, thanks @crisf.codes. For your specific case, a workaround some people use is to leverage the setContext link, which is Promise friendly. You can see an example of this (using AsyncStorage) here:

...
const httpLink = new HttpLink({ uri: '/graphql' });
let token;

const withToken = setContext(async request => {
  if (!token) {
    token = await AsyncStorage.getItem('token');
  }
  return {
    headers: {
      authorization: token
    }
  };
});

const resetToken = onError(({ networkError }) => {
  if (networkError && networkError.statusCode === 401) {
    // remove cached token on 401 from the server
    token = undefined;
  }
});

const authFlowLink = withToken.concat(resetToken);
const link = authFlowLink.concat(httpLink);
...

If you would like to see this supported in onError, would you mind opening a feature request? Thanks!

@hwillson thanks to point to this, although this is a possible solution for simple cases, it does not apply to more complex cases, for example what if I need to perform other asynchronous processes such as updating a user’s token if the server’s token has expired. How can I implement this flow?

Take as an example these steps, which are frequently performed if I need to refresh the user token:.

  1. Send some request with auth token as header.
  2. If the token’s expired, the server sends back a 401 error code.
  3. Retrieve from storage(Asyncsotage) the refresh token that was stored in login proccess.
  4. Send the refresh token to the server to generate a new pair of tokens.
  5. Store new pair of tokens in Asyncstorage.

In some way I need to apply async login in onError method…