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

Global catcher #162

Closed
viclafouch opened this issue Jan 11, 2023 · 7 comments
Closed

Global catcher #162

viclafouch opened this issue Jan 11, 2023 · 7 comments
Labels

Comments

@viclafouch
Copy link

viclafouch commented Jan 11, 2023

Hello !

I'm facing an issue that I don't understand and I don't find anything on the documentation.

Here my code

const request = wretch(baseURL, {
  cache: 'default',
  headers
})
  .errorType('json')
  .resolve((response) => {
    return response.json()
  })
  .addon(QueryStringAddon)


export function getRestaurantPaymentModes({ cartId }) {
  return request.get(`/carts/v3/${cartId}/payment/modes`)
}

When I have an error, I would like to receive the JSON without doing error.message every time.. Is it possible to do it globally ?

Like, I want to manipulate the error before accessing it, but globally.

try {
  await getRestaurantPaymentModes({ cartId: 1 })
} catch (error) {
  console.log(error) // From `error.message`
  // my json error
}

Someone can help plzz ?

@elbywan
Copy link
Owner

elbywan commented Jan 11, 2023

Hey @viclafouch!

I'm facing an issue that I don't understand and I don't find anything on the documentation.

The documentation for .errorType() is located here.

Someone can help plzz ?

Yes. 😄

In your example you are logging the error itself console.log(error), but the json body lives inside the .json property.
Try replacing that with console.log(error.json)

<script type="module">
    import wretch from 'https://cdn.skypack.dev/wretch';

    const baseURL = "https://api-gateway.frichti.co"
    const cartId = 100

    const request = wretch(baseURL)
      .errorType('json')
      .resolve((response) => {
        return response.json()
      })

    try {
      await request.get(`/carts/v3/${cartId}/payment/modes`)
    } catch (error) {
      console.log(error.json)
      // => {"error":{"code":"UNKNOWN_ERROR","details":"N/A","message":"Available payment methods not found: invalid input syntax for type uuid: \"100\""}}
    }
</script>

@elbywan
Copy link
Owner

elbywan commented Jan 11, 2023

Oh wait, you edited the original comment so I'm not sure if I'm replying to the right thing.

When I have an error, I would like to receive the JSON without doing error.message every time.. Is it possible to do it globally ?

It should be available under the error.json property without you having to parse the message.

Like, I want to manipulate the error before accessing it, but globally.

I'm not sure I understand this use case. If the goal is to mutate the error globally then you could do something like this (reusing the same example):

const request = wretch(baseURL)
  .errorType('json')
  .resolve((response) => {
    return response
      .notFound((err) => { console.log(err.whatever = "yeah"); throw err })
      .json()
  })

try {
  await request.get(`/carts/v3/${cartId}/payment/modes`)
} catch (error) {
  console.log(error.json)
  // => {"error":{"code":"UNKNOWN_ERROR","details":"N/A","message":"Available payment methods not found: invalid input syntax for type uuid: \"100\""}}
  console.log(err.whatever)
  // => "yeah"
}

@viclafouch
Copy link
Author

Thanks @elbywan for your reply !

@viclafouch
Copy link
Author

viclafouch commented Jan 11, 2023

@elbywan

Here what I'm doing, tell me if you find anything wrong.

To explain, I want to override the WretchError and use my own custom Error for all requests. Here, I create an ApiError, this is my custom Error, and use the catch of wretch to override the error.

export class ApiError {
  constructor(code, message, status, json = null) {
    // The message error from the backend, or a fallback
    this.message = message || 'An unknown error has occurred'
    // The code error from the backend, or a fallback
    this.code = code || 'UNKNOWN_ERROR'
    // In some rare cases, we want to access to the json error (maybe some data exist)
    this.json = json
    // The status of the error (e.g: 404, 403, 500, ...)
    this.status = status
  }
}

const request = wretch(baseUrl, {
  cache: 'default',
  headers
})
  .errorType('json')
  .resolve((response) => {
    return response.json().catch((error) => {
      const { json } = error
      throw new ApiError(
        // Don't read that, my backend is far to be perfect 😂
        json?.error?.code ?? json?.code,
        json?.error?.message ?? json?.message ?? json?.details,
        error.status,
        json
      )
    })
  })

try {
  await request.get(`/carts/v3/${cartId}/payment/modes`)
} catch (error) {
  console.log(error instanceof ApiError) // true
  console.log(error.code) // my Code
}

Are we agree that the catch will 'catch' every errors ?

Like, doing this :

wretch("...")
  .get()
  .badRequest((err) => console.log(err.status))
  .unauthorized((err) => console.log(err.status))
  .forbidden((err) => console.log(err.status))
  .notFound((err) => console.log(err.status))
  .timeout((err) => console.log(err.status))
  .internalError((err) => console.log(err.status))
  .error(418, (err) => console.log(err.status))
  .fetchError((err) => console.log(err))
  .res();

is equivalent to this :

wretch("...")
  .get()
  .errorType('json')
  .forbidden(() => console.log('I can still using the method, to track something'))
  .resolve((response) => {
    return response.json().catch((error) => {
      console.log(error.status)
      // every single error here (internalError, badRequest, fetchError, etc...)
    })
  })

@elbywan
Copy link
Owner

elbywan commented Jan 11, 2023

@viclafouch

Here what I'm doing, tell me if you find anything wrong.

Hmm I don't think this will work because the resolver prepends (!= appends) to the response chain. The reason from what I recall is that it is impossible from the library side to precisely know when then chain ends.

For instance this code (from the docs):

const w = wretch()
  .addon(PerfsAddon())
  .resolve(resolver => resolver
    .perfs(console.log)
    .json()
  )

Is conceptually equivalent to:

w
  .url("http://a.com")
  .get()
// <- the resolver chain is automatically injected here, just after the .get() call!
  .perfs(console.log)
  .json()

In your example since the resolver chain injects .json() which returns a Promise, calling .notFound or other catchers is not possible.

.get()
// adding .forbidden() is not possible because the resolver prepends .json() before
// .json() <- Returns a Promise
.forbidden(() => console.log('I can still using the method, to track something'))

So one solution would be to use .catcher which can be added early on in the request chain (before .get()):

const request = wretch(baseURL)
  .errorType('json')
  .resolve((response) => {
    return response.json().catch((error) => {
      console.log("global catch")
     })
  })


await request
  .catcher(404, err => console.log("not found", err.json))
  .get(`/carts/v3/${cartId}/payment/modes`)

Another easy solution would be to drop .json() from the resolver and call it at the end of each chain.

There is an unwanted behaviour right now that gives higher priority to the global catchers compared to the specific ones but I'm going to publish a new version in a short notice that will allow the following code to work:

const request = wretch(baseURL)
  .errorType("json")
  .resolve((response) => {
    return (
      response
        // .json()
        .error("Error", (error) => {
          console.log("global catch (Error class)");
        })
        .error("TypeError", (error) => {
          console.log("global type error catch (TypeError class)");
        })
    );
  });

await request
  .get(`/carts/v3/${cartId}/payment/modes`)
  .notFound((error) => {
    console.log("not found");
  })
  .json();

elbywan added a commit that referenced this issue Jan 11, 2023
Specifying the name of an error had a higher priority when cathing errors than statuses, which was never intended.
See #162 on how it allows to create global error listeners using resolvers.
@elbywan
Copy link
Owner

elbywan commented Jan 11, 2023

Allright 2.3.2 is published and the above code should work properly now. 📦

@viclafouch
Copy link
Author

OO nice ! Thank you !

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

No branches or pull requests

2 participants