Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

An exchange for all errors #225

Closed
jackfranklin opened this issue Apr 12, 2019 · 6 comments
Closed

An exchange for all errors #225

jackfranklin opened this issue Apr 12, 2019 · 6 comments

Comments

@jackfranklin
Copy link

jackfranklin commented Apr 12, 2019

Hello,

Sorry if I'm missing a trick, but I can't figure out how to create an exchange that runs after the fetchExchange. I want to do this because we'd like to log to Sentry every time a GraphQL request fails, so we're aware of it.

The problem is that I don't think fetchExchange ever forwards things on (other than things that aren't a thing it understands), and therefore any exchange that's after fetchExchange won't get any data.

Is that correct, and if so, is there a way around this? The other thing I can think of is making our own fetchExchange that does this but I'm not sure if that's a great idea.

Thanks!

Jack

@kitten
Copy link
Member

kitten commented Apr 12, 2019

Hiya, no need to be sorry! This is a very valid question and we're still working on more documentation around custom exchanges.

So, generally you'll want to run a fire-and-forget request after a GraphQL request has either had a network error or GraphQL errors, I presume?

Exchanges are bi-directional. So suppose you have the default order: [dedupExchange, cacheExchange, fetchExchange], then an operation describing the GraphQL request, which is only the intent to send a request, goes from outer to inner, or left to right. It'll reach dedup first, then cache, then fetch. This is the operation stream.

The operation result stream goes the other way. fetch might emit a result, which is seen by cache, and then seen by dedup. This is a little abstract and we will need some visuals to make this an accessible concept to everyone.

This is how it works because every exchange receives a stream of operations. It can then transform this stream and call forward with an altered stream (so every exchange has full control over filtering, mapping, reducing, timing of operations). Furthermore, every return value of an exchange is a stream of results.

This means that the simplest exchange just forwards all operations and returns an unaltered stream of results: ({ forward }) => ops$ => forward(ops$). For your "error exchange" (which we should probably provide by default?) this means that it must come before the fetch exchange: [dedupExchange, cacheExchange, errorExchange, fetchExchange]

Specifically, it won't need to alter the operations probably, but it will need to look at the results from the fetchExchange which means it must be outside fetch or come before it. Here's an example of a simple implementation of such an exchange:

import { filter, pipe, tap } from 'wonka';
import { Exchange } from 'urql';

export const errorExchange: Exchange = ({ forward }) => ops$ => {
  return pipe(
    forward(ops$),
    tap(({ error }) => {
      // If the OperationResult has an error send a request to sentry
      if (error) {
        // the error is a CombinedError with networkError and graphqlErrors properties
        sentryFireAndForgetHere() // Whatever error reporting you have
      }
    })
  );
};

So we don't alter the input or output streams but instead just tap the results and send a fire-and-forget, as a side-effect.

Hope this helps! Let me know if you still need some more input 👍

@jackfranklin
Copy link
Author

@kitten this is awesome, thanks! I completely missed that exchanges go in both ways.

I think providing a default error exchange that calls some function you give it would be a useful thing to provide for sure. For now I'll take that and put it into our codebase :)

@jackfranklin
Copy link
Author

@kitten thanks for your help but unfortunately I'm not having much luck with this.

Here's how we create our client:

const defaultExchanges = [
  dedupExchange,
  cacheExchange,
  errorExchange, // this is our custom one
  fetchExchange,
];

export const createGraphQLClient = (extraOpts = {}) =>
  createClient({
    url: '/graphql',
    fetchOptions: {
      credentials: 'same-origin',
      headers: {
        'X-CSRFToken': getCookie('csrftoken'),
      },
    },
    exchanges: defaultExchanges,
    ...extraOpts,
  });

const defaultClient = createGraphQLClient();

And here's the errorExchange:

import { pipe, tap } from 'wonka';

export const errorExchange = ({ forward }) => ops$ => {
  console.log('error exchange was used');
  return pipe(
    forward(ops$),
    tap(result => {
      console.log('GOT HERE', result);
    })
  );
};

If I purposefully break the query, I don't see the console.log in the browser. I do see the error exchange was used call (I wanted to make sure I'd done that bit right!) but I don't see the GOT HERE call.

The error does make it through to the useQuery data, so I think I'm setting up the exchange wrong?

Additionally, even if I make the query succeed, I don't see the GOT HERE log from errorExchange (which I would expect, because it's not currently caring about if there was an error or not).

Any pointers would be very welcome, thank you!

@jackfranklin
Copy link
Author

@kitten ignore my last request, turns out we weren't using our custom client as I thought (see #227 for thoughts on that!). This is working, thank you :)

@scottrippey
Copy link

FWIW, since Google led me here ...

URQL now includes an errorExchange and this example is right in the docs:

import { createClient, dedupExchange, fetchExchange, cacheExchange, errorExchange } from 'urql';
const client = createClient({
  url: '/graphql',
  exchanges: [
    dedupExchange,
    cacheExchange,
    errorExchange({
      onError(error) {
        console.error(error);
      },
    }),
    fetchExchange,
  ],
});

@drc37
Copy link

drc37 commented Dec 14, 2023

And for others, this errorExchange has been deprecated. https://formidable.com/open-source/urql/docs/api/core/#errorexchange-deprecated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants