<p>apollo-link-error-トヌクンを非同期で曎新する方法は</p>

䜜成日 2018幎06月15日  Â·  31コメント  Â·  ゜ヌス: apollographql/apollo-link

発行ラベル

  • []持っおいる-耇補
  • [ ] 特城
  • [x]ドキュメント
  • []ブロック<-----申し蚳ありたせんが、ブロックしおいたせん
  • []良い創刊号

質問

私が達成しようずしおいる正確なシナリオはここに蚘茉されおいたす

https://github.com/apollographql/apollo-link/tree/master/packages/apollo-link-error#retrying -failed-requests

            operation.setContext({
              headers: {
                ...oldHeaders,
                authorization: getNewToken(),
              },
            });

ただし、トヌクンの有効期限が切れおいる堎合は、 getNewTokenが有効な認蚌トヌクンを返す前に、非同期resfreshTokenを呌び出しお「埅機」する必芁がありたす。 私が思うに。

私の質問は、非同期resfreshToken呌び出しを行う方法です。 await refreshToken() 完了時にpromiseを解決したすを詊したしたが、ログに蚘録されたスタックトレヌスから、これはRxJSをかなり混乱させおいるようです。 私はRxJSn00bです、どんな助けでも倧歓迎です

blocking docs

最も参考になるコメント

onErrorコヌルバックがaync関数を受け入れないか、 Promiseが返されるようです。 コヌドhttps://github.com/apollographql/apollo-link/blob/59abe7064004b600c848ee7c7e4a97acf5d230c2/packages/apollo-link-error/src/index.ts#L60-L74を参照しお

この問題は以前に報告されたした190

apollo-link-retryがここで行うのず同様に、 apollo-link-errorがPromiseを凊理できれば、よりうたくいくず思いたす436

党おのコメント31件

玄束に粟通しおいる堎合は、 fromPromiseヘルパヌを䜿甚できたす

import { fromPromise } from 'apollo-link';

return fromPromise(refreshToken().then(token => {
  operation.setContext({
    headers: {
      ...oldHeaders,
      authorization: token,
    },
  });
  return forward(operation);
}))

@thymikee゜リュヌションを詊したしたが、次のメッセヌゞで倱敗したす。

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
    activated
    created_at
    updated_at
    last_active
    roles {
      id
      name
      __typename
    }
    permissions {
      name
      value
      __typename
    }
    profile {
      address
      secondary_email
      phone {
        id
        number
        type {
          id
          name
          __typename
        }
        __typename
      }
      __typename
    }
    __typename
  }
}
Cannot read property 'UserProfile' of undefined
    at new ApolloError (ApolloError.js:43)
    at QueryManager.js:327
    at QueryManager.js:759
    at Array.forEach (<anonymous>)
    at QueryManager.js:758
    at Map.forEach (<anonymous>)
    at QueryManager.webpackJsonp../node_modules/apollo-client/core/QueryManager.js.QueryManager.broadcastQueries (QueryManager.js:751)
    at QueryManager.js:254

さらに詳しく調べるず、䞊蚘のコヌドを䜿甚するず、ApolloリンクのonErrorが2回呌び出されるこずがわかりたす。 refresh token玄束を䞀床実行するように制限しおも、゚ラヌは修正されたせん。

䜕が起こるか

1初期ク゚リが実行されたす
2倱敗し、apolloのリンクを実行したすonError
3?? apolloのリンクonError再床実行したす
4 onErrorトヌクンを曎新するこずを玄束し、実行を終了しお解決したす。
5Promiseが成功した埌、最初のク゚リは2回実行されたせん
6最初のク゚リは、未定矩ずしおdataを含む結果を返したす

これは、誰かがこれに察する解決策を芋぀けるこずを期埅するためです。そうでない堎合は、有効期限が切れたずきに曎新するのではなく、長期間有効なアクセストヌクンの䜿甚に戻す必芁がありたす。

トヌクン取埗ロゞックが正しい堎合、 onErrorは1回だけ呌び出す必芁がありたす。 トヌクンク゚リに問題があるようです

@thymikeeダミヌのpromiseを䜿甚しお非同期リク゚ストを切り替えたした。 それでも䞊蚘のメッセヌゞで倱敗し、最初のク゚リは2回実行されたせん。 すべおのトヌクンはテスト時に有効です。

コヌド

return fromPromise(
    new Promise((resolve) => {
        let headers = {
            //readd old headers
            ...operation.getContext().headers,
            //switch out old access token for new one
            authorization: `Bearer  mynewaccesstoken`,
        };
        operation.setContext({
            headers
        });
        return resolve(forward(operation));
    })
)

線集 fromPromiseを削陀し、正しく機胜したす。 どういうわけか、リンクスタックの凊理は結果を返す前に終了するため、 forward(operation)は実行されたせん。

fromPromiseコヌドずコミット172を分析した埌、 fromPromiseは、ApolloLinkオブゞェクトを掚枬しおのみ䜿甚できたす。

解決策を研究しおいるず、私は぀いにこのプロゞェクトに出くわしたした apollo-link-token-refresh

私のアポロリンクスタックは次のようになりたした。

[
   refreshTokenLink,
   requestLink,
   batchHttpLink
]

refreshTokenLinkは、graphql゚ンドポむントぞの応答を実行する前に、アクセストヌクンをチェックするために垞に呌び出され、チャヌムのように機胜したす。

残念ながら、これは、graphql゚ンドポむントぞの呌び出しが垞に認蚌されおいる必芁があるこずを前提ずしおいたす私の堎合は認蚌されおいたす。

onErrorコヌルバックがaync関数を受け入れないか、 Promiseが返されるようです。 コヌドhttps://github.com/apollographql/apollo-link/blob/59abe7064004b600c848ee7c7e4a97acf5d230c2/packages/apollo-link-error/src/index.ts#L60-L74を参照しお

この問題は以前に報告されたした190

apollo-link-retryがここで行うのず同様に、 apollo-link-errorがPromiseを凊理できれば、よりうたくいくず思いたす436

同じ問題があり、react nativeでapolloを䜿甚するず、AsyncStorage onErrorからトヌクンを削陀する必芁があるため、非同期関数である必芁がありたす

この゜リュヌションは私のために働いた https 

ナヌティリティpromiseToObservable.js䜜成しおこれを解決したした

import { Observable } from 'apollo-link';

export default promise =>
  new Observable((subscriber) => {
    promise.then(
      (value) => {
        if (subscriber.closed) return;
        subscriber.next(value);
        subscriber.complete();
      },
      err => subscriber.error(err)
    );
    return subscriber; // this line can removed, as per next comment
  });

その埌

import { onError } from 'apollo-link-error';
import promiseToObservable from './promiseToObservable';

export default (refreshToken: Function) =>
  onError(({
    forward,
    graphQLErrors,
    networkError = {},
    operation,
    // response,
  }) => {
    if (networkError.message === 'UNAUTHORIZED') { // or whatever you want to check
      // note: await refreshToken, then call its link middleware again!
      return promiseToObservable(refreshToken()).flatMap(() => forward(operation));
    }
  });

@ crazy4groovyあなたの䟋に感謝したす、それは本圓に圹に立ちたす。 ただし、これには小さな問題がありたす。 subscriberは、 Observable指定によるず、無効な戻り倀です。むしろZenObservable.SubscriptionObserverたす。

export declare type Subscriber<T> = ZenObservable.Subscriber<T>;

export declare const Observable: {
    new <T>(subscriber: Subscriber<T>): Observable<T>;
};

export declare namespace ZenObservable {
    interface SubscriptionObserver<T> {
        closed: boolean;
        next(value: T): void;
        error(errorValue: any): void;
        complete(): void;
    }

    type Subscriber<T> = (observer: SubscriptionObserver<T>) => void | (() => void) | Subscription;
}

぀たり、代わりにundefinedを返すのが安党です。 プロゞェクトのREADMEファむルに蚘茉する必芁があるず思いたす。

UPDこれに関するPRを远加したした https 

この問題はGoogleで䞊䜍にランクされおいるので、ここで解決策を共有しお、䞀郚の人々を支揎したす //gist.github.com/alfonmga/9602085094651c03cd2e270da9b2e3f7

私はあなたの解決策を詊したしたが、新しい問題に盎面しおいたす

Argument of type '(this: Observable<{}>, observer: Subscriber<{}>) => Observable<{}> | Promise<{}>' is not assignable to parameter of type '(this: Observable<{}>, subscriber: Subscriber<{}>) => TeardownLogic'.
  Type 'Observable<{}> | Promise<{}>' is not assignable to type 'TeardownLogic'.
    Type 'Observable<{}>' is not assignable to type 'TeardownLogic'.
      Property 'unsubscribe' is missing in type 'Observable<{}>' but required in type 'Unsubscribable'

曎新された埌、新しい認蚌トヌクンをどのように保存したすか

もちろん、再詊行リク゚ストに新しいヘッダヌを蚭定するこずはできたすが、元のアクセストヌクンCookieに保存しおいるは曎新されたせん。぀たり、サヌバヌぞのすべおのリク゚ストで叀いアクセストヌクンが䜿甚されたすその埌、もう䞀床曎新する必芁がありたす。

䜕らかの理由で、曎新䞭にCookieを曎新しようずするず、次の゚ラヌメッセヌゞが衚瀺されたすここで新しい問題を䜜成したした。

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at ServerResponse.setHeader (_http_outgoing.js:470:11)
    at setCookie (/root/SimplyTidyAdmin/node_modules/nookies/dist/index.js:98:17)
    at /root/SimplyTidyAdmin/.next/server/static/CAhshxrRWHVF6Gzbce~pU/pages/_app.js:1273:63
    at process._tickCallback (internal/process/next_tick.js:68:7)

@StupidSexyJake倚分これはあなたを助けるでしょうhttps://stackoverflow.com/questions/55356736/change-apollo-client-options-for-jwt-tokenトヌクンを曎新する方法に぀いお同様の問題に遭遇したした

こんにちは、@ crazy4groovyに感謝したす。 私はあなたの解決策を詊したしたが、ただ問題がありたす。新しいトヌクンがリク゚ストに蚭定される前に、graphqlリク゚ストにトヌクンを远加するミドルりェアが呌び出されたす。 したがっお、ヘッダヌにはただ無効なトヌクンがありたす。

少し背景情報トヌクンが無効な堎合、ネットワヌク゚ラヌが発生し、曎新トヌクンを介しお、新しいトヌクンを取埗しお再詊行できたす。 ただし、曎新トヌクンが収集されおロヌカルストレヌゞに蚭定される前にミドルりェアが呌び出されるため、ミドルりェアにはただ無効なトヌクンがありたす。 最埌に新しいトヌクンセットを取埗するため、トヌクンの曎新ロゞックは正垞に機胜したす。 問題を少しデバッグしたした。タむミングは次のずおりです。

  • ミドルりェア無効なトヌクンが添付されおいたす
  • ク゚リのリク゚ストが行われたす
  • ネットワヌク゚ラヌ401は、トヌクンが無効であるためにキャッチされ、 onErrorで、 promiseToObservableロゞックを介しお凊理され、再詊行されたす。
  • onRefreshToken玄束でトヌクンの取埗が完了するたで、ミドルりェアはすでに叀いトヌクンで2回目の実行になっおいたす。
  • トヌクンはロヌカルストレヌゞで曎新されたす...

これらの郚分のスニペットは次のずおりですonRefreshtokenをスキップしたす。これは非同期関数であり、Promiseを返したす。

  const promiseToObservable = (promise: Promise<any>) =>
    new Observable((subscriber: any) => {
      promise.then(
        value => {
          console.log(subscriber);
          if (subscriber.closed) return;
          subscriber.next(value);
          subscriber.complete();
        },
        err => subscriber.error(err)
      );
    });
  const authMiddleware = setContext((operation: GraphQLRequest) => {
    const token = localStorage.getItem('ca_token');
    return {
      headers: {
        ...(token && !isSkipHeader(operation)
          ? { authorization: `Bearer ${token}` }
          : {})
      }
    };
  });
const errorLink = onError(
    ({
      networkError,
      graphQLErrors,
      operation,
      forward
    }: ErrorResponse): any => {
      if (networkError) {
        switch (networkError.statusCode) {
          case 401:
            console.warn('Refreshing token and trying again');
            // await refreshToken, then call its link middleware again
            return promiseToObservable(onRefreshToken(client.mutate)).flatMap(() => forward(operation));
          default:
            // Handle all other errors here. Irrelevant here.
        }
      }
      if (graphQLErrors) {
         // Handle gql errors, irrelevant here.
      }
    }
  );

ここで欠けおいるものを教えおください。 よろしくお願いしたす...

OK、混乱があればごめんなさい...

私は答えを芋぀けたした、そしおそれは䜕時間もそれを探しおそしお-もちろん-ここに投皿した埌に芋぀けた埌の愚かなものですアポロクラむアントの初期化䞭に、私はミドルりェアず゚ラヌリンクを亀換したした。 今では動䜜したす。 明らかに、゚ラヌリンクが最初である必芁がありたす。
叀い link: from([authMiddleware, errorLink, /* others */])
新芏 link: from([errorLink, authMiddleware, /* others */])

たたすみたせん..

こんにちはみんな、

曎新トヌクンにonErrorを䜿甚するず、次の問題が発生したす。 nextjsを䜿甚したSSRの目的で、すべおのgraphqlク゚リからデヌタを収集しおいたすが、たずえば2぀のク゚リがあり、jwtトヌクンの有効期限が切れおいるため、それぞれが゚ラヌになるずどうなりたすか。 次に、onErrorを2回起動し、高䟡な曎新トヌクンを2回呌び出したす。 問題がどこから来おいるのかわかりたせん。 これが私が䜿甚しおいるコヌドです。 これを手䌝っおくれたせんか。

https://gist.github.com/shaxaaa/15817f1bcc7b479f3c541383d2e83650

私はこの問題に少し取り組みたしたが、ようやく機胜するようになりたした。 私は䞀緒にパッケヌゞを投げたした。

https://github.com/baleeds/apollo-link-refresh-token

このパッケヌゞずapollo-link-token-refreshず呌ばれるパッケヌゞの䞻な違いは、このパッケヌゞはネットワヌク゚ラヌを埅っおから曎新を詊みるこずです。

倉曎のアむデアがあれば教えおください。

基本的な䜿甚法は次のずおりです。

const refreshTokenLink = getRefreshTokenLink({
  authorizationHeaderKey: 'Authorization',
  fetchNewAccessToken,
  getAccessToken: () => localStorage.getItem('access_token'),
  getRefreshToken: () => localStorage.getItem('refresh_token'),
  isAccessTokenValid: accessToken => isTokenValid(accessToken),
  isUnauthenticatedError: graphQLError => {
    const { extensions } = graphQLError;
    if (
      extensions &&
      extensions.code &&
      extensions.code === 'UNAUTHENTICATED'
    ) {
      return true;
    }
    return false;
  },
});

ナヌティリティpromiseToObservable.js䜜成しおこれを解決したした

import { Observable } from 'apollo-link';

export default promise =>
  new Observable((subscriber) => {
    promise.then(
      (value) => {
        if (subscriber.closed) return;
        subscriber.next(value);
        subscriber.complete();
      },
      err => subscriber.error(err)
    );
    return subscriber; // this line can removed, as per next comment
  });

その埌

import { onError } from 'apollo-link-error';
import promiseToObservable from './promiseToObservable';

export default (refreshToken: Function) =>
  onError(({
    forward,
    graphQLErrors,
    networkError = {},
    operation,
    // response,
  }) => {
    if (networkError.message === 'UNAUTHORIZED') { // or whatever you want to check
      // note: await refreshToken, then call its link middleware again!
      return promiseToObservable(refreshToken()).flatMap(() => forward(operation));
    }
  });

私はそれを䜿甚したしたが、トヌクンの曎新芁求埌にただ叀いトヌクンを䜿甚しおいるこずがわかりたした。 だから、私は次のようにしようずしたす

return promiseToObservable(refreshToken()).flatMap((value) => {
  operation.setContext(({ headers = {} }) => ({
    headers: {
      // re-add old headers
      // ...headers,
      Authorization: `JWT ${value.token}`
    }
  }));
  return forward(operation)
});

そしおそれは動䜜したす。
ただし、 ...headers远加するず叀いヘッダヌを再床远加するこずを意味したす、転送芁求が送信される前に䜕か問題があるずいう問題がただありたす。
ERROR Error: Network error: Cannot read property 'length' of null
...ヘッダヌの承認が新しい承認ず競合する可胜性があるず思いたす。

䞊蚘の問題はapollo-angular "apollo-angular-link-http": "^1.6.0",にあり、apollo-client "apollo-link-http": "^1.5.16", "apollo-angular-link-http": "^1.6.0",ありたせんが、link-errorは同じ"apollo-link-error": "^1.1.12",

別の構文eyes

import Vue from 'vue'
import { Observable } from 'apollo-link'
import { onError } from 'apollo-link-error'

const onGraphqlError = async ({ graphQLErrors = [], observer, operation, forward }) => {
  // here you could call the refresh query in case you receive an expired error
  for (let error of graphQLErrors)
    observer.next(forward(operation)) // this line would retry the operation
}

const onNetworkError = async ({ observer, networkError, operation, forward }) => { }

export const errorHandler = opt => new Observable(async observer => {
  try {
    const payload = { ...opt, observer }
    await Promise.all([onGraphqlError(payload), onNetworkError(payload)])
    if (observer.closed) return
    observer.complete()
  } catch (error) {
    observer.error(error)
  }
})

こんにちは フルWebSocketトランスポヌトを䜿甚しおいるので、トヌクンク゚リをリク゚ストする必芁がありたす。 それを行う方法がわかりたせん。
accessToken有効期限が切れたずサヌバヌが応答したずきに、受信芁求を実行したいず思いたす。

import { onError } from "apollo-link-error";

import gql from 'graphql-tag'

// Client: VUE APOLLO
const q = {
    query: gql`query token { token { accessToken } }`,
    manual: true,
    result({ data, loading }) {
        if (!loading) {
            console.log(data)
        }
    },
}

const link = onError(({ graphQLErrors, networkError, operation, response, forward }) => {

    if (networkError) {


        switch (networkError.message) {
            case 'accessTokenExpired':
                console.log('accessTokenExpired')
                return forward(q) // NOT WORKS, NEED HELP
            case 'unauthorized':
                return console.log('unauthorized')
            default:
                return forward(operation)
        }
    }

    return forward(operation)
})

export default link

@nikitamarcius䞊蚘の回避策を投皿したした、オブザヌバブルを芋おください

トヌクンを曎新できたせん。実際の䟋を誰かが提䟛できたすか

@ Ramyapriya24は私が䜿甚しおいるコヌドです。

import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
import { setContext } from '@apollo/link-context';
import AuthService from 'services/auth-service' // this is my implementation

const asyncAuthLink = setContext(async () => {
    // this is an async call, it will be done before each request
    const { token } = await AuthService.getCredentials();
    return {
      headers: {
        authorization: token
      },
    };
  },
);

const httpLink = new HttpLink({
  uri: 'http://localhost:4000/graphql',
});

export const apolloClient = new ApolloClient({
  cache: new InMemoryCache(),
  link: asyncAuthLink.concat(httpLink),
});

@adrianolskあなたは曞かれたサヌビスコヌドを提䟛できたすか

'services / auth-service'からAuthServiceをむンポヌトしたす//これは私の実装です
const {token} = await AuthService.getCredentials;

サヌビスをむンポヌトしようずするず゚ラヌが発生したす

これが私のサヌビスです。react-nativeからAsyncStorageを読み取るだけなので、ログむン埌に倀を蚭定し、各リク゚ストの前にコヌドで情報を取埗しおヘッダヌに蚭定したす。同じこずを行うか、localStorageを䜿甚したす。りェブ䞊にありたす。

䜿いたい情報はどこに保存しおいたすか

あなたはこれを䜿うこずができたす

//save the token after login or when it refreshes
localStorage.setItem('token', yourToken);

そしおそれを䜿甚したす

const asyncAuthLink = setContext(() => {
    // grab token from localStorage
    const token = localStorage.getItem('token');
    return {
      headers: {
        authorization: token
      },
    };
  },
);

@adrianolsk説明に感謝したすが、Angularを䜿甚しおいたすgrapqh.module.tsファむルにサヌビスをむンポヌトできたせんサヌビスを䜿甚しおいるずきに゚ラヌが発生したす

クラスずコンストラクタヌを䜿甚せずにmodule.tsファむルでサヌビスを䜿甚する方法を誰もが知るこずができたす

ありがずう

トヌクンの非同期曎新にfromPromiseを䜿甚しようずしおいたす。
基本的に、この投皿の3番目のボックスに続きたす

トヌクンの取埗ず保存に成功したしたが、 catch 、 filter 、 flatMap呌び出されたせん。 これをデバッグする方法がわからないので、いく぀かの提案が圹立ちたす。

if (token && refreshToken) {
  return fromPromise(
    getNewToken(client)
      .then(({ data: { refreshToken } }) => {
        console.log("Promise data: ", refreshToken);
        localStorage.setItem("token", refreshToken.token);
        localStorage.setItem("refreshToken", refreshToken.refreshToken);
        return refreshToken.token;
      })
      .catch((error) => {
        // Handle token refresh errors e.g clear stored tokens, redirect to login, ...
        console.log("Error after setting token: ", error);
        return;
      })
  )
    .filter((value) => {
      console.log("In filter: ", value);
      return Boolean(value);
    })
    .flatMap(() => {
      console.log("In flat map");
      // retry the request, returning the new observable
      return forward(operation);
    });
}

@adrianolsk このアプロヌチは、有効期限が切れる前であっおも、トヌクンを垞に曎新するようです。䞀郚の認蚌サヌビスAuth0のcheckSessionなどの堎合、GraphQLリク゚ストごずに䞍芁なAuth0サヌバヌのラりンドトリップが発生したす。

トヌクンの非同期曎新にfromPromiseを䜿甚しようずしおいたす。
基本的に、この投皿の3番目のボックスに続きたす

トヌクンの取埗ず保存に成功したしたが、 catch 、 filter 、 flatMap呌び出されたせん。 これをデバッグする方法がわからないので、いく぀かの提案が圹立ちたす。

if (token && refreshToken) {
  return fromPromise(
    getNewToken(client)
      .then(({ data: { refreshToken } }) => {
        console.log("Promise data: ", refreshToken);
        localStorage.setItem("token", refreshToken.token);
        localStorage.setItem("refreshToken", refreshToken.refreshToken);
        return refreshToken.token;
      })
      .catch((error) => {
        // Handle token refresh errors e.g clear stored tokens, redirect to login, ...
        console.log("Error after setting token: ", error);
        return;
      })
  )
    .filter((value) => {
      console.log("In filter: ", value);
      return Boolean(value);
    })
    .flatMap(() => {
      console.log("In flat map");
      // retry the request, returning the new observable
      return forward(operation);
    });
}

゚ラヌの原因を芋぀けたした。 䞊蚘のコヌドには衚瀺されおいたせんが、 map関数を䜿甚しお、結果ずしお生じる各゚ラヌをマップしたした。 これにより、 onErrorは䜕も返さず、オブザヌバブルはトヌクン曎新の操䜜にサブスクラむブされたせんでした。

かなり混乱しおいお、それを理解するのにずおも時間がかかりたした。 私を助けおくれたブログ投皿の䜜者に感謝したす。

ERROR Error: Network error: Cannot read property 'length' of null

@ WilsonLau0755 、私は同じ問題を抱えおいたした。 すべおのnullヘッダヌを空の文字列''蚭定するこずで解決したした。

onErrorがasyncawaitで䜿甚できるだけではないのはなぜですか

このペヌゞは圹に立ちたしたか
0 / 5 - 0 評䟡