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

How to add a hook that always runs on success and failure? #212

Closed
IGx89 opened this issue Dec 15, 2023 · 2 comments
Closed

How to add a hook that always runs on success and failure? #212

IGx89 opened this issue Dec 15, 2023 · 2 comments

Comments

@IGx89
Copy link

IGx89 commented Dec 15, 2023

Hello! I really like this library you've come up with and am starting to convert a project over to it (from Axios). I've run into a use case however that I can't quite port. I have code that needs to run at the end of every request, that does different things based on whether an error was thrown or not. I thought middlewares would be the way to do so, however the middleware runs before the response is processed and the catchers are run. Scenarios where it breaks:

  • Response is not "ok" (could check for response.ok in the middleware, like the retry middleware does, but doesn't handle the 404 example below)
  • Response is not "ok" but the individual request has a notFound (for example) that swallows the exception and thus makes it effectively "ok".

One example use case is a middleware/addon that sets an error message/success message on the screen based on whether the request succeeded or not. I can't do that reliably currently, since there's no way to know for certain if an error was thrown or not. I could use catcherFallback to always know if an error was thrown (though there can only be one so that's not perfect), but I'd still have no way to run code when an error wasn't thrown.

Apologies if I'm not explaining things properly! I'm basically looking for a way to globally effectively add a .then and .catch onto every request call.

elbywan added a commit that referenced this issue Dec 30, 2023
@elbywan
Copy link
Owner

elbywan commented Dec 30, 2023

Hey @IGx89 sorry for the late reply,

I'm basically looking for a way to globally effectively add a .then and .catch onto every request call.

I just released v2.8.0 which implements an easy way to achieve this through addons:

const fetcher = wretch("https://jsonplaceholder.typicode.com").addon({
  // resolver can now be a function and to re-use the previously defined methods of the response chain
  resolver: (chain) => ['res', 'json', 'text', 'blob', 'formData', 'arrayBuffer'].reduce((acc, method) => ({
    ...acc,
    // overrides .json, .text… methods to chain .then & .catch
    [method](cb) {
      return chain[method](cb)
        .then(ret => (console.log('[hook] ok')))
        .catch(error => {
          console.error('[hook] error', error.response.url, error.response.status)
          throw error
        });
    }
  }), {})
});

(async function () {
  await fetcher.get("/todos/1").json(console.log);
  console.log("-----");

  // { userId: 1, id: 1, title: 'delectus aut autem', completed: false }
  // [hook] ok
  // -----

  await fetcher.get("/bad-route").notFound(error => {
    console.log('handled error :)')
  }).text(console.log);
  console.log("-----");

  // handled error :)
  // [hook] ok
  // -----

  await fetcher.get("/bad-route").text(console.log).catch(() => console.log('unhandled error :('));

  // [hook] error https://jsonplaceholder.typicode.com/bad-route 404
  // unhandled error :(
})()

@IGx89
Copy link
Author

IGx89 commented Jan 2, 2024

Thanks so much! That ended up working perfectly :). Figuring out the proper types took a little time though, so sharing what I ended up with here in case it helps someone else:

export interface WretchRequestConfig {
  suppressError?: boolean;
  suppressLoading?: boolean;
}
...
wretch.addon({
  beforeRequest: (wretch, opts: RequestInit & WretchRequestConfig) => {
    this.updateLoading(true, opts.suppressLoading);
    return wretch;
  },
  resolver: (chain: WretchResponseChain<unknown>) => (['res', 'json', 'text', 'blob', 'formData', 'arrayBuffer'] as const).reduce((acc, method) => ({
    ...acc,
    [method]: (cb: (type: unknown) => unknown) => {
      const opts = (chain._wretchReq._options as RequestInit & WretchRequestConfig);
      return chain[method](cb)
        .then((result) => {
          this.updateError(chain._wretchReq._url, '', true, opts.suppressError);
          return result;
        })
        .catch((error: WretchError) => this.handleError(error, chain._wretchReq._url, !!chain._wretchReq._options.suppressError))
        .finally(() => this.updateLoading(false, opts.suppressLoading));
    }
  }), {})
} as WretchAddon<unknown, unknown>);

@IGx89 IGx89 closed this as completed Jan 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants