当前位置: 首页 > 知识库问答 >
问题:

如何执行异步获取请求,然后重试上次失败的请求?

融烨磊
2023-03-14

Apollo link提供了一个错误处理程序onError

问题:目前,我们希望在apollo调用期间过期时刷新oauth令牌,并且我们无法在onError中正确执行异步获取请求。

代码:

<代码>初始化阿波罗客户端。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. 初始图形QL查询运行并因未经授权而失败
  2. 执行ApolloLinkonError函数。
  3. 刷新令牌的promise已执行。
  4. 再次执行ApolloLinkonError函数??
  5. 刷新令牌的promise完成。
  6. 返回初始的graph QL查询结果,其数据为未定义

在第5步和第6步之间,apollo不会重新运行初始失败的graph QL查询,因此结果是未定义

控制台错误:

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. 操作失败时运行异步请求

共有3个答案

齐昊焱
2023-03-14

我们只是有同样的问题,在一个非常复杂的解决方案之后,有很多可观察的东西,我们得到了一个简单的解决方案,使用promise,最终将被包装成可观察的东西。

let tokenRefreshPromise: Promise = Promise.resolve()
let isRefreshing: boolean

function createErrorLink (store): ApolloLink {
  return onError(({ graphQLErrors, networkError, operation, forward }) => {
    if (graphQLErrors) {
      // this is a helper method where we are checking the error message
      if (isExpiredLogin(graphQLErrors) && !isRefreshing) {
        isRefreshing = true
        tokenRefreshPromise = store.dispatch('authentication/refreshToken')
        tokenRefreshPromise.then(() => isRefreshing = false)
      }
      return fromPromise(tokenRefreshPromise).flatMap(() => forward(operation))
    }
    if (networkError) {
      handleNetworkError(displayErrorMessage)
    }
  })
}

所有挂起的请求都在等待tokenRefreshPromise,然后将被转发。

凌远
2023-03-14

接受的答案很好,但它不适用于2个或更多并发请求。我在使用适合我需求的令牌更新工作流程测试了不同的情况后制作了下面的一个。

有必要在链接管道中的authLink之前设置errorLink<代码>客户端。ts

import { ApolloClient, from, HttpLink } from '@apollo/client'

import errorLink from './errorLink'
import authLink from './authLink'
import cache from './cache'

const httpLink = new HttpLink({
  uri: process.env.REACT_APP_API_URL,
})

const apiClient = new ApolloClient({
  link: from([errorLink, authLink, httpLink]),
  cache,
  credentials: 'include',
})

export default apiClient

2个apollo客户端实例之间共享的缓存,用于在续订令牌过期时设置用户查询

缓存。ts

import { InMemoryCache } from '@apollo/client'

const cache = new InMemoryCache()

export default cache

AuthLink.ts

import { ApolloLink } from '@apollo/client'

type Headers = {
  authorization?: string
}

const authLink = new ApolloLink((operation, forward) => {
  const accessToken = localStorage.getItem('accessToken')

  operation.setContext(({ headers }: { headers: Headers }) => ({
    headers: {
      ...headers,
      authorization: accessToken,
    },
  }))

  return forward(operation)
})

export default authLink

<代码>错误链接。ts

import { ApolloClient, createHttpLink, fromPromise } from '@apollo/client'

import { onError } from '@apollo/client/link/error'

import { GET_CURRENT_USER } from 'queries'
import { RENEW_TOKEN } from 'mutations'

import cache from './cache'

let isRefreshing = false
let pendingRequests: Function[] = []

const setIsRefreshing = (value: boolean) => {
  isRefreshing = value
}

const addPendingRequest = (pendingRequest: Function) => {
  pendingRequests.push(pendingRequest)
}

const renewTokenApiClient = new ApolloClient({
  link: createHttpLink({ uri: process.env.REACT_APP_API_URL }),
  cache,
  credentials: 'include',
})

const resolvePendingRequests = () => {
  pendingRequests.map((callback) => callback())
  pendingRequests = []
}

const getNewToken = async () => {
  const oldRenewalToken = localStorage.getItem('renewalToken')

  const {
    data: {
      renewToken: {
        session: { renewalToken, accessToken },
      },
    },
  } = await renewTokenApiClient.mutate({
    mutation: RENEW_TOKEN,
    variables: { input: { renewalToken: oldRenewalToken } },
  })!

  localStorage.setItem('renewalToken', renewalToken)
  localStorage.setItem('accessToken', accessToken)
}

const errorLink = onError(({ graphQLErrors, operation, forward }) => {
  if (graphQLErrors) {
    for (const err of graphQLErrors) {
      switch (err?.message) {
        case 'expired':
          if (!isRefreshing) {
            setIsRefreshing(true)

            return fromPromise(
              getNewToken().catch(() => {
                resolvePendingRequests()
                setIsRefreshing(false)

                localStorage.clear()

                // Cache shared with main client instance
                renewTokenApiClient!.writeQuery({
                  query: GET_CURRENT_USER,
                  data: { currentUser: null },
                })

                return forward(operation)
              }),
            ).flatMap(() => {
              resolvePendingRequests()
              setIsRefreshing(false)

              return forward(operation)
            })
          } else {
            return fromPromise(
              new Promise((resolve) => {
                addPendingRequest(() => resolve())
              }),
            ).flatMap(() => {
              return forward(operation)
            })
          }
      }
    }
  }
})

export default errorLink
劳麒
2023-03-14

我正在以这种方式刷新令牌(更新操作):

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 }) => {
      // User access token has expired
      if (graphQLErrors && 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);
              });
          });
        }
      }
    })
  ])
});
 类似资料:
  • 下面的代码我得到当我使用异步.当我使用同步时,它成功了。 链接失败,但在异步和同步情况下都成功。 出什么事了?这是在Python3.4中实现的。2在FreeBSD8上,aiohttp 0.14。4,请求2.5。3. 此操作的输出为:

  • 问题内容: 我已经看到您可以指定如果ajax请求失败通常要做什么,是否可以让它在某种循环中重试,以便它在停止之前尝试至少x次?我以前使用过此代码: 这将适用于所有AJAX请求(如果我输入错了,请纠正我)。 我想做这样的事情: 这行得通吗?甚至更好:这是正确的方法吗?我会将重试包装在计数系统中,以便不会无限重试。在我的应用程序中,十分之九会正常工作,但是我经常与之交互的API之一有时会返回错误。 任

  • 问题内容: 我有一个提供标准扩展点的JavaScript小部件。其中之一是功能。它应返回以防止创建项目。 我已经使用jQuery在此函数中添加了Ajax调用: 但是我想防止我的小部件创建项目,所以我应该在母函数中返回,而不是在回调中返回。有没有一种方法可以使用jQuery或任何其他浏览器内API执行同步AJAX请求? 问题答案: 从jQuery文档开始:您将 异步 选项指定为 false, 以获取

  • t[object对象]。(/home/ubuntu/node_modules/mongodb/lib/mongodb/connection/server.js:530:7)在[object object]。在[object object]发出(events.js:88:20)。(/home/ubuntu/node_modules/mongodb/lib/mongodb/connection/con

  • 我试图建立一个基本的网页,允许用户在数据库的mysql表中添加一行,以便打开LED灯和使用arduino。然而,当我尝试提交表单时,页面返回500状态,mysql查询没有执行。我在我的电脑上运行linux-apache-mysql-php服务器。为什么会这样?

  • 我不知道我改变了什么,但是获取请求不再工作了(只使用Android)。它以前工作过,试图改变它回来,但不能让它工作。 我尝试了本期中的链接:React本地网络请求在获取数据时失败 我试图获取的数据 图像以便更好地查看