Michael
Michael

Reputation: 42050

How to improve my error handling of this chain of promises?

I am new to TypeScript/JavaScript and Node.js and coming from Java/Scala background.
I am writing a simple script in TypeScript to collect some data and send them as an HTTP POST request to a server using axios.

makePromiseToGetA()
  .then((a: A) => makePromiseToGetB(a))
  .then((b: B) => sendHttpRequestAxios(b))
  .then((resp: AxiosResponse) => outputResponse(resp))
  .catch((err: Error) => handleError(err))

Now I want to improve the error handling. Specifically, I want to handle AxiosError using my function handleAxiosError(ae: AxiosError) in addition to the generic error handling (function handleError)

Now I see two options to do that:

  1. Modify the handleError function like this:

    // pseudocode because I don't know how to code this in TypeScript
    
    function handleError(err: Error): void { 
    
      if (err instanceof AxiosError) {
        handleAxiosError(err as AxiosError);
      }
      ... // handle Error
    }
    
  2. "catch" AxiosError after sendHttpRequestAxios, handle the error, and then re-throw it:

    makePromiseToGetA()
      .then((a: A) => makePromiseToGetB(a))
      .then((b: B) => sendHttpRequestAxios(b).catch((ae: AxiosError) => {handleAxiosError(ae); throw ae;}))
      .then((resp: AxiosResponse) => outputResponse(resp))
      .catch((err: Error) => handleError(err))
    

How would you suggest handle AxiosError with handleAxiosError in addition to generic error handling using handleError ?

Upvotes: 1

Views: 425

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074088

Your #1 seems like a reasonable solution to me if you generally want to handle AxiosError instances differently from other errors. The problem with #2 is that you'll end up handling the error twice: Once in the Axios-specific rejection handler and then again in handleError.

If you don't like that instanceof approach (in handleError or in the rejection handler at the end), you can use nesting:

makePromiseToGetA()
  .then((a: A) => makePromiseToGetB(a))
  .then((b: B) =>
    sendHttpRequestAxios(b)
      .then((resp: AxiosResponse) => outputResponse(resp))
      .catch((err: AxiosError) => handleAxiosError(ae))
  )
  .catch((err: Error) => handleError(err))

That takes advantage of the fact that the Axios part is the last non-rejection part of the chain. So you handle it via handleAxiosError, converting rejection to fulfillment — but nothing uses the resulting fulfillment, so you're good. If some other error occurs, though, you end up in the final rejection handler.


Side note: It's just an example and your real code is probably more complex (though perhaps the rejection handlers aren't), but when passing a fulfillment value or rejection reason to a function as its argument, there's no need for a wrapper arrow function:

makePromiseToGetA()
  .then(makePromiseToGetB)
  .then((b: B) =>
    sendHttpRequestAxios(b)
      .then(outputResponse)
      .catch(handleAxiosError)
  )
  .catch(handleError)

Upvotes: 1

Related Questions