elbywan / wretch

A tiny wrapper built around fetch with an intuitive syntax. :candy:
MIT License
4.79k stars 96 forks source link

Catchers not found on reusable wretch instance #47

Closed tmartensen closed 5 years ago

tmartensen commented 5 years ago

Hey, great library!

Question: I've set up a reusable wretch instance that looks somewhat like the following. Also, I'm having a weird time using wretch without having to use default, see below:

const wretch = require("wretch").default;

const csrfMiddleware = next => (url, opts) => {
  const csrfToken = window.localStorage.getItem("csrfToken");
  opts.headers = { ...opts.headers, "x-csrf-token": csrfToken };
  return next(url, opts);
};

export const apiFetch = wretch()
  .url(`/v1`)
  .accept("application/json")
  .content("application/json")
  .options({
    credentials: "same-origin"
  })
  .middlewares([csrfMiddleware])
  .resolve(resolver => resolver
    .res(response => {
      window.localStorage.setItem("csrfToken", response.headers.get("x-csrf-token"));
      return response.json();
    })
    .catch(error => {
      window.localStorage.setItem("csrfToken", error.response.headers.get("x-csrf-token"));
      throw error;
    })
  );

Then, when I try to use the notFound catcher in a subsequent call, it errors, saying that the function chain with notFound in it is not a function:

return apiFetch
  .url(`/foo`)
  .query({
    foo: 'bar'
  })
  .get()
  .notFound(() => {
    return {
      foo: `quux`
    };
  }) // this results in Uncaught TypeError: apiFetch.url(...).query(...).get(...).notFound is not a function
  .then(json => json)
  .catch(error => {
    console.error(error);
  });

Any suggestions on the right way to accomplish what I'm doing here?

elbywan commented 5 years ago

Hey @tmartensen,

Hey, great library!

Thanks!

I'm having a weird time using wretch without having to use default

It's because of the way typescript transpiles default exports to commonjs (see this issue for more details).

But I see an export in your code, so you could just use import instead of require and it should work.

Then, when I try to use the notFound catcher in a subsequent call, it errors, saying that the function chain with notFound in it is not a function:

Yes, it's because you are missing a response type in your chain.

return apiFetch
  .url(`/foo`)
  .query({
    foo: 'bar'
  })
  .get()
  .notFound(() => {
    return {
      foo: `quux`
    };
  })
  .json() // <- with this it should work.
  .then(json => json)
  .catch(error => {
    console.error(error);
  });

Actually I was a bit quick to answer, I see that you used a resolver in the reusable wretch. Inside the resolver, you use .res() already.

The problem is that the resolver gets "inserted" in the chain right after the http verb (.get()in this case).

This would be the equivalent of doing:

return apiFetch
  .url(`/foo`)
  .query({
    foo: 'bar'
  })
  .get()
  .res(/* ... */) // <- the resolver chain is automatically injected here 
  .catch(/* ... */)
  .notFound(() => { // <- .res().catch().notFound() is invalid
    return {
      foo: `quux`
    };
  }
  .then(json => json)
  .catch(error => {
    console.error(error);
  });

I would suggest putting the .resolve part inside the middleware.

const csrfMiddleware = next => async (url, opts) => {
  const csrfToken = window.localStorage.getItem("csrfToken");
  opts.headers = { ...opts.headers, "x-csrf-token": csrfToken };
  const response = await next(url, opts);
  // clone the response, inspect the json and perform the localstorage action accordingly.
  return response
};

Or you could use the early .catcher(404, ...) before the .get call instead of .notFound. It should work too.

tmartensen commented 5 years ago

@elbywan thanks for the quick response! When I try to use an import statement, I get this: api.js?0361:10 Uncaught TypeError: (0 , _wretch2.default) is not a function.

Could it be because I'm using a Webpack build and it's picking up the module file (dist/index.js) instead of the main file (dist/bundle/wretch.js)?

elbywan commented 5 years ago

Could it be because I'm using a Webpack build and it's picking up the module file (dist/index.js) instead of the main file (dist/bundle/wretch.js)?

I think that it's very likely that the error is caused by the bundler config, but on the other hand I'm not sure that the issue is related to the module file.

dist/index.js is quite small and the code is pretty standard:

import { Wretcher } from "./wretcher";
var factory = Wretcher.factory;
factory["default"] = Wretcher.factory;
/* Export typescript types */
export { Wretcher } from "./wretcher";
/**
 * Return a fresh Wretcher instance.
 */
export default factory;
//# sourceMappingURL=index.js.map

I also tried replicating your problem by using the getting started webpack setup, but writing a simple import statement was enough to make it work:

import wretch from 'wretch'

So unfortunately I don't really know why in your case it fails, webpack configs can be quite complex. :confused: If you are using babel-loader, you could also check your .babelrc config.

tmartensen commented 5 years ago

@elbywan Thanks for your help! Closing...