Apollo-client: ApolloLink throws an error when updating the response using async/await

Created on 15 Nov 2020  Â·  3Comments  Â·  Source: apollographql/apollo-client


Hello,

I have been trying to mutate the response data using ApolloLink, I have tried several workarounds using the following sandbox: https://codesandbox.io/s/practical-fire-vp1li?file=/src/MyApolloClient.js

Sample link to modify response data:

const mutatorLink = new ApolloLink((operation, forward) => {
  return forward(operation).map(async (response) => {
    await someAsyncLogic(response);
    return response;
  });
});

Intended outcome:

The application should not crash and the response data should include "status" as one of the keys.

Actual outcome:

The application crashed but the response data included the "status" key.

How to reproduce the issue:

Use the attached sandbox to reproduce this issue. There exists several links in the 'MyApolloClient.js' file, simply disable/enable any of the 3 links.
Enabling link 1 or 3 will end up in throwing an error that's related to storing the item in the cache:

 Error writing result to store for query: {"kind":"Document","definitions":
[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetMedia"},"variableDefinitions":
[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":
{"kind":"Name","value":"Page"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":
{"kind":"IntValue","value":"1"}},{"kind":"Argument","name":{"kind":"Name","value":"perPage"},"value":
{"kind":"IntValue","value":"3"}}],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":
{"kind":"Name","value":"pageInfo"},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":
[{"kind":"Field","name":{"kind":"Name","value":"total"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"currentPage"},"arguments":[],"directives":[]},{"kind":"Field","name":
{"kind":"Name","value":"lastPage"},"arguments":[],"directives":[]},{"kind":"Field","name":
{"kind":"Name","value":"hasNextPage"},"arguments":[],"directives":[]},{"kind":"Field","name":
{"kind":"Name","value":"perPage"},"arguments":[],"directives":[]}]}},{"kind":"Field","name":
{"kind":"Name","value":"media"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"search"},"value":
{"kind":"StringValue","value":"Fate/Zero","block":false}}],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":
[{"kind":"Field","name":{"kind":"Name","value":"id"},"arguments":[],"directives":[]},{"kind":"Field","name":
{"kind":"Name","value":"title"},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":
[{"kind":"Field","name":{"kind":"Name","value":"romaji"},"arguments":[],"directives":[]}]}}]}}]}}]}}],"loc":{"start":0,"end":271}} 
undefined is not an object (evaluating 'result[resultFieldKey]')

^ undefined is not an object (evaluating 'result[resultFieldKey]'

Enabling only link 2 will modify the response successfully without a crash, however, it is stuck in the loading state.

Versions

apollo-client: 2.6.10
Apollo-boost: 0.4.9
apollo-link: 1.2.14
apollo-link-http: 1.5.17

--
I have tried find some answers on how to properly manipulate the response using async/await or promises, with no result.. Any help on this would be grateful.

Most helpful comment

@benjamn Thank you for your significant input. Yes, it did work and this solution is convenient for me. I am hoping to contribute to the docs to include this workaround, to help future readers.

All 3 comments

@alichry Unfortunately Observable.prototype.map does not handle async functions automatically, so the Promise object is getting passed back to the client, instead of the result. I agree it would be convenient if you could use an async function here, but the Observable API would need to change, and I don't have control over that.

Although you're still using Apollo Client 2.x, you might be interested in the asyncMap helper function that we use internally, which you could use as follows:

// NOTE: This requires installing @apollo/client (AC3):
import { asyncMap } from "@apollo/client/utilities";

const mutatorLink = new ApolloLink((operation, forward) => {
  return asyncMap(forward(operation), async (response) => {
    await someAsyncLogic(response);
    return response;
  });
});

If that works, and you don't want to include @apollo/client/utilities in your bundle, you can copy/paste the asyncMap code into your project—just make sure you have Observable in scope where asyncMap is defined.

@benjamn Thank you for your significant input. Yes, it did work and this solution is convenient for me. I am hoping to contribute to the docs to include this workaround, to help future readers.

Hello again @benjamn đź‘‹

The upcoming editorial pass specifies that it is possible to add new fields to the response object, however, I was not able to do so. Would you please confirm? So we can have an accurate documentation.

Please see this sandbox: https://codesandbox.io/s/nice-buck-rgl4r?file=/src/MyApolloClient.js

Was this page helpful?
0 / 5 - 0 ratings