简体   繁体   中英

Promise catch() order in complex promise return chain

How is the error caught, when I return a promise from function A to function B, return the data from A's then, and both calls catch on the Promise? I know, when promise is resolved, A's then always gets executed first, followed by B's then with the data returned from A's then. But I can't wrap my head around how errors are caught, when this kind of returning of Promises forms a long chain. Here is the simplified sample of the scenario. I'm using Redux-thunk action creators to manage the state.

function postActionCreator(data) {
  return (dispatch) => {
    dispatch(type: POST_LOADING)
    return Source.post()
      .then(response => {
        dispatch(type: POST_SUCCESS, payload: response)
        return response
      })
      .catch(error => {
        // Is this catch called if handlePost throws error in then?
        dispatch(type: POST_ERROR, payload: error)
        throw new Error(error)
      })
  }
}

// Container component's post function calling the Redux action creator
function handlePost(data) {
  this.props.postActionCreator(data)
    .then(response => {
      // Do something with response
    })
    .catch(error => {
      // Or is the error caught with this catch?
    })
}

// Or are the both catchs called, in which order and why?

How is the error handled in these three different scenarios:

  • Source.post throws and error
  • postActionCreator's then throws an error
  • handlePost's then throws an error

When using promises, a function should do one of three things:

  1. Return a value
  2. Return a promise
  3. Throw an error

For this question, we're not too concerned with the first two cases, but you can read about the promises resolution procedure here for more information. So let's take a look at that error case.

In JavaScript, errors - like most things - are just objects. Creating an error and choosing how to propagate that error are two different things. The two broad categories of propagating errors are synchronously and asynchronously. To propagate an error synchronously, you must throw it, for async, you just pass your error object through some predefined convention (such as a callback or promise).

To answer the question fully, we need to understand how to handle these two different error types. For synchronous errors (that have been thrown), the only way to handle them (aside from a catch all event handler like window.onerror ) is to wrap them in a try/catch statement. For async errors, we just follow the conventions of how to pass this data back up the call stack.

So to answer your question with that knowledge:

Source.post throws an error

If I assume by "throws an error" you mean that "an error occurred", we can't know how this will behave without knowing the source code of Source.post . If an error was actually thrown, let's say there's some unexpected ReferenceError , then it won't actually be handled at all:

function post() {
  foo.bar; // will throw
}

function run() {
  post()
    .then(log)
    .catch(log);
}

Would result in:

ReferenceError: foo is not defined
    at post (sync.js:6:3)
    at run (sync.js:10:3)
    at Object.<anonymous> (sync.js:15:1)

Now if the post function actually handled an error asynchronously, in this case by confirming to the promise conventions for passing errors, we'd see that it would get caught:

function post() {
  return new Promise((resolve, reject) => {
    reject('foo');
  });
}

function run() {
  post()
    .then(() => {})
    .catch((err) => {
      console.error('Caught error:', err);
    });
}

Results in

Caught error: foo

One more interesting part to this is that your code, in the catch statement actually throws a new Error object. In this case, we have one final thing to understand. We know that throwing an error synchronously means that it must be caught, but throwing an error from within a then function results in a rejected exception, not an error, so what's going on? Well, the promise implementation is internally wrapping the function you pass to then in a try/catch block, then handling this error by rejecting the promise. We can demonstrate this like so:

function post() {
  return new Promise((resolve, reject) => {
    resolve('foo');
  });
}

function run() {
  post()
    .then((result) => {
      throw result;
    })
    .catch((err) => {
      console.error('Caught error:', err);
    });
}

In this case, the error is also caught.

postActionCreator's then throws an error

This now becomes simple. An error in a then is caught and propagated. It reaches the catch within postActionCreator then is rethrown to the outside catch .

handlePost's then throws an error

The simplest case. It'll be caught internally and you'll get the error in the catch statement immediately after the then .


And finally, you may be thinking, "how can I handle those synchronous errors in Source.post ? What if that's not my function?". Good question! You can use a utility like promise.try from Bluebird to wrap this function for you.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM