复杂承诺返回链中的承诺 catch() 顺序

Posted

技术标签:

【中文标题】复杂承诺返回链中的承诺 catch() 顺序【英文标题】:Promise catch() order in complex promise return chain 【发布时间】:2016-11-23 08:42:19 【问题描述】:

当我从函数 A 向函数 B 返回一个 Promise 时,如何捕获错误,然后从 A 返回数据,并且两个调用都捕获 Promise?我知道,当 promise 被解决时,A 总是首先被执行,然后是 B,然后是从 A 返回的数据。但是当这种 Promise 的返回形成一条长链时,我无法理解如何捕获错误。这是场景的简化示例。我正在使用 Redux-thunk 动作创建者来管理状态。

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?

这三种不同场景下的错误是如何处理的:

Source.post 抛出和错误 postActionCreator 会抛出错误 handlePost 会引发错误

【问题讨论】:

1) 两个捕获,2) 两个捕获(尽管您发布的代码从未在其中抛出),3) handlePost 的捕获 【参考方案1】:

使用 Promise 时,函数应该做以下三件事之一:

    返回一个值 返回一个承诺 抛出错误

对于这个问题,我们不太关心前两种情况,但您可以在此处阅读promises resolution procedure 以获取更多信息。那么让我们来看看那个错误案例。

javascript 中,错误 - 就像大多数事情一样 - 只是对象。创建错误和选择如何传播该错误是两件不同的事情。传播错误的两大类是同步的和异步的。要同步传播错误,您必须throw 它,对于异步,您只需通过一些预定义的约定(例如回调或承诺)传递错误对象。

要全面回答这个问题,我们需要了解如何处理这两种不同的错误类型。对于同步错误(已经抛出),处理它们的唯一方法(除了像window.onerror 这样的捕获所有事件处理程序)是将它们包装在try/catch 语句中。对于异步错误,我们只需遵循如何将这些数据传回调用堆栈的约定。

所以用这些知识回答你的问题:

Source.post 抛出错误

如果我假设“抛出错误”是指“发生错误”,那么如果不知道Source.post 的源代码,我们就无法知道它的行为方式。如果真的抛出了一个错误,假设有一些意想不到的ReferenceError,那么它实际上根本不会被处理:

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


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

会导致:

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

现在,如果 post 函数实际上异步处理了一个错误,在这种情况下,通过确认传递错误的承诺约定,我们会看到它会被捕获:

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


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

结果

Caught error: foo

其中一个更有趣的部分是,您的代码在catch 语句中实际上抛出了一个新的Error 对象。在这种情况下,我们还有最后一件事要理解。我们知道同步抛出错误意味着它必须被捕获,但是从 then 函数中抛出错误会导致被拒绝的异常,而不是错误,那么这是怎么回事呢?好吧,promise 实现是在内部将传递给then 的函数包装在try/catch 块中,然后通过拒绝promise 来处理这个错误。我们可以这样演示:

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


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

在这种情况下,错误也被捕获。

postActionCreator 会抛出错误

现在这变得简单了。 then 中的错误被捕获并传播。它到达postActionCreator 内的catch,然后被重新抛出到外部catch

handlePost 会抛出错误

最简单的情况。它将在内部被捕获,并且您将在 then 之后立即在 catch 语句中收到错误。


最后,您可能会想,“我该如何处理Source.post 中的那些同步错误?如果那不是我的功能怎么办?”。好问题!您可以使用 Bluebird 中的 promise.try 之类的实用程序为您包装此函数。

【讨论】:

很好的解释,这是我读过的最清晰最详细的答案之一

以上是关于复杂承诺返回链中的承诺 catch() 顺序的主要内容,如果未能解决你的问题,请参考以下文章

返回承诺链中的引用数据

如何按顺序执行承诺并返回所有结果[重复]

承诺按顺序运行嵌套承诺并在第一次拒绝时解决

承诺链中断言的捕获错误

如何编写测试用例来覆盖承诺链中所有嵌套的“then”回调

承诺链中承诺之间的延迟