How to execute an async fetch request and then retry last failed request?

左心房为你撑大大i 提交于 2019-11-28 23:00:19

问题


Apollo link offers an error handler onError

Issue: Currently, we wish to refresh oauth tokens when they expires during an apollo call and we are unable to execute an async fetch request inside the onError properly.

Code:

initApolloClient.js

import { ApolloClient } from 'apollo-client';
import { onError } from 'apollo-link-error';
import { ApolloLink, fromPromise } from 'apollo-link';

//Define Http link
const httpLink = new createHttpLink({
    uri: '/my-graphql-endpoint',
    credentials: 'include'
});

//Add on error handler for apollo link

return new ApolloClient({
    link: ApolloLink.from([
        onError(({ graphQLErrors, networkError, operation, forward  }) => {
            if (graphQLErrors) {
                //User access token has expired
                if(graphQLErrors[0].message==="Unauthorized") {
                    //We assume we have both tokens needed to run the async request
                    if(refreshToken && clientToken) {
                        //let's refresh token through async request
                        return fromPromise(
                            authAPI.requestRefreshToken(refreshToken,clientToken)
                            .then((refreshResponse) => {
                                let headers = {
                                    //readd old headers
                                    ...operation.getContext().headers,
                                    //switch out old access token for new one
                                    authorization: `Bearer ${refreshResponse.access_token}`,
                                };

                                operation.setContext({
                                    headers
                                });

                                //Retry last failed request
                                return forward(operation);
                            })
                            .catch(function (error) {
                                //No refresh or client token available, we force user to login
                                return error;
                            })
                        )
                    }
                }
            }
        }
    }
}),

What happens is:

  1. Initial graphQL query runs and fails due to unauthorization
  2. The onError function of ApolloLink is executed.
  3. The promise to refresh the token is executed.
  4. The onError function of ApolloLink is executed again??
  5. The promise to refresh the token is completed.
  6. The initial graphQL query result is returned and its data is undefined

Between step 5 and 6, apollo doesn't re-run the initial failed graphQL query and hence the result is undefined.

Errors from console:

Uncaught (in promise) Error: Network error: Error writing result to store for query:
 query UserProfile($id: ID!) {
  UserProfile(id: $id) {
    id
    email
    first_name
    last_name
    }
    __typename
  }
}

The solution should allow us to:

  1. Run an async request when an operation fails
  2. Wait for the result of the request
  3. Retry failed operation with data from the request's result
  4. Operation should succeed to return its intended result

回答1:


I'm refreshing the token this way (this is updated your code):

import { ApolloClient } from 'apollo-client';
import { onError } from 'apollo-link-error';
import { ApolloLink, Observable } from 'apollo-link'; // <-- Add Observable

// Define Http link
const httpLink = new createHttpLink({
  uri: '/my-graphql-endpoint',
  credentials: 'include'
});

// Add on error handler for apollo link

return new ApolloClient({
  link: ApolloLink.from([
    onError(({ graphQLErrors, networkError, operation, forward }) => {
      if (graphQLErrors) {
        // User access token has expired
        if(graphQLErrors[0].message === "Unauthorized") {
          // We assume we have both tokens needed to run the async request
          if(refreshToken && clientToken) {
            // Let's refresh token through async request
            return new Observable(observer => {
              authAPI.requestRefreshToken(refreshToken, clientToken)
                .then(refreshResponse => {
                  operation.setContext(({ headers = {} }) => ({
                    headers: {
                      // Re-add old headers
                      ...headers,
                      // Switch out old access token for new one
                      authorization: `Bearer ${refreshResponse.access_token}` || null,
                    } 
                  }))
                })
                .then(() => {
                  const subscriber = {
                    next: observer.next.bind(observer),
                    error: observer.error.bind(observer),
                    complete: observer.complete.bind(observer)
                  }

                  // Retry last failed request
                  forward(operation).subscribe(subscriber)
                })
                .catch(error => {
                  // No refresh or client token available, we force user to login
                  observer.error(error)
                })
            })
          }
        }
      }
    })
  ])
})


来源:https://stackoverflow.com/questions/50965347/how-to-execute-an-async-fetch-request-and-then-retry-last-failed-request

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!