web-dev-qa-db-ja.com

非同期フェッチ要求を実行して、最後に失敗した要求を再試行する方法は?

ApolloリンクはエラーハンドラーonError を提供します

問題:現在、oauthトークンがアポロコール中に期限切れになり、実行できない場合、トークンを更新します。 onError内の非同期フェッチ要求。

コード:

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;
                            })
                        )
                    }
                }
            }
        }
    }
}),

何が起こるかです:

  1. 最初のgraphQLクエリが実行され、認証解除が原因で失敗する
  2. onErrorApolloLink関数が実行されます。
  3. トークンを更新する約束が実行されます。
  4. onErrorApolloLink関数が再度実行されますか??
  5. トークンを更新する約束が完了しました。
  6. 初期graphQLクエリ結果が返され、そのデータはundefinedです

ステップ5と6の間で、apolloは最初に失敗したgraphQLクエリを再実行しないため、結果はundefinedになります。

コンソールからのエラー:

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
  }
}

ソリューションにより、次のことが可能になります。

  1. 操作が失敗したときに非同期リクエストを実行する
  2. リクエストの結果を待つ
  3. リクエストの結果からのデータで失敗した操作を再試行します
  4. 操作は、意図した結果を返すことに成功するはずです
12
Mysteryos

私はこの方法でトークンを更新しています(これはあなたのコードを更新します):

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)
                })
            })
          }
        }
      }
    })
  ])
})
27
Igor Lamoš