我如何知道哪些处理程序在 promise 中抛出错误?

Posted

技术标签:

【中文标题】我如何知道哪些处理程序在 promise 中抛出错误?【英文标题】:How do I know which handlers throw error in promise? 【发布时间】:2017-01-06 12:34:23 【问题描述】:

假设我有如下承诺:

p.then(Task1)
 .then(Task2)
 .then(Task3)
 .catch(errorHandler);

Task2遇到错误时,如何知道错误来自catch中的Task2

【问题讨论】:

在Task2中你可以做try...code catch(err)return Promise.reject("rejected at task 2: " + err) 最简单的方法是让每个Task抛出一个有意义的错误 @Redu try/catch 仅适用于同步错误。我收集到 OP 也想从 Task2 中捕获异步错误。 @jib 异步错误非常容易。您只需进行异步调用并返回返回的 Promise 并在下一阶段的 fulfilledrejected 回调中处理结果或错误。任何结果或错误都应返回到下一阶段处理。我想可能有三件事出错了。 1) 一个不受控制的同步错误(尝试 catch 并从 catch 块中拒绝) 2) API 返回承诺被拒绝.. 只需将其返回到下一个 then 阶段,3) 受控错误(如数据不足或缺少参数)返回一个 @ 987654325@ 你将如何在同步代码中做到这一点? 【参考方案1】:

大家!我自己研究过演示代码。

希望大家能点评一下我的回答,好不好。


简介:

它展示了如何在每个处理程序中跟踪承诺,使用定制的错误处理程序来捕获错误。了解promise的工作流程。

您可以复制以下演示代码并粘贴到您的 node.js 中。根据示例和日志信息,开发者学习promise是有好处的。


用到的promise模块如下:

蓝鸟

演示代码如下:

var Promise = require('bluebird');

// You can input any argument in this function to test workflow of promise 
function testPromise(input) 
    let promise = Promise.resolve(input);
    promise
      .then(makeTask('Task1'))
      .then(makeTask('Task2'))
      .then(makeTask('Task3'))
      .catch(makeErrorPredicate('Task1'), taskLog('Task1'))
      .catch(makeErrorPredicate('Task2'), taskLog('Task2'))
      .catch(makeErrorPredicate('Task3'), taskLog('Task3'))


// define task handler functions
function makeTask(task) 
    return function task1Handler(input) 
        if (input === task) 
            throw new Error(task)
                    
        return input
    


// custom error that it checks error message 
function makeErrorPredicate(msg) 
    return function taskError(err) 
        var result = err.message === msg;
        console.log(msg + ': ' + result)
        return result;
    


// hint the error when the error has matched
function taskLog(msg) 
    return function thelog(err) 
        console.log('It\'s ' + msg)
      

例子:

>testPromise('Task1')
Task1: true
It's Task1

>testPromise('Task2')
Task1: false
Task2: true
It's Task2

>testPromise('Task3')
Task1: false
Task2: false
Task3: true
It's Task3

从上面的例子我们可以知道:

输入为'Task1'时,路由为:

firstHandler -> firstCatcher

输入为'Task2'时,路由为:

firstHandler -> secondHandler -> firstCatcher -> secondCather

输入为'Task3'时,路由为:

firstHandler->secondHandler->thirdHandler->firstCatcher->secondCatcher->thirdCatcher

所以,从上面我们知道的结果,我们可以理解promise是如何工作的。


如果每个人都对这个答案感到满意,请告诉我,谢谢。

【讨论】:

makeTaskHandler 应该命名为makeTaskmakeThrowTask,而makeTaskError 应该命名为makeErrorPredicatemakeMessageMatcher 之类的。 @Bergi 我同意你的建议!【参考方案2】:

由于 Promise 链无法保存此信息,因此您需要将其存储在某个地方。两种解决方案:

修饰错误:

p.then(Task1)
 .then(function() 
    try 
      Task2();
     catch (e) 
      e.isFromTask2 = true;
      throw e;
     
  )
 .then(Task3)
 .catch(errorHandler); // now you can check err.isFromTask2

使用承诺链的作用域:


  let isFromTask2 = false;
  p.then(Task1)
   .then(function() 
      try 
        Task2();
       catch (e) 
        isFromTask2 = true;
        throw e;
       
    )
   .then(Task3)
   .catch(errorHandler); // now you can check isFromTask2

【讨论】:

try/catchTask2 异步时不起作用。请参阅下面的 Redu 和 Jib 的答案。【参考方案3】:

大多数浏览器都支持error.stack 属性,一些现代浏览器甚至支持异步调用栈:

let a = () => Promise.resolve();
let b = () => Promise.reject(new Error("Fail"));
let c = () => Promise.resolve();

Promise.resolve().then(a).then(b).then(c).catch(e => console.log(e.stack));

在 Firefox 48 中,它输出:b@http://stacksnippets.net/js:14:30

在 Chrome 52 中,它输出:Error: Fail at b (http://stacksnippets.net/js:14:30)

这是最惯用的解决方案,因为它不会干扰代码的编写方式。不幸的是,并不是所有的浏览器都支持异步调用堆栈,而且输出因浏览器而异,因为它是用于调试的。

【讨论】:

这从字面上回答了“我怎么知道错误来自哪里”的问题,但我怀疑 OP 想知道“......以及如何根据来源处理它”,为此堆栈跟踪不是那么有用。 error.stack 并不是所有浏览器都真正支持 - 这可能会在缩小代码中失败。 @BenjaminGruenbaum 从链接中的兼容性表来看,至少大多数桌面浏览器似乎都支持。【参考方案4】:

为了在所有浏览器中知道最终捕获中的(异步)错误来自Task2,您可以捕获它、标记它并重新抛出它:

var tag = (e, name) => (e.isFrom = name, Promise.reject(e));

p.then(Task1)
 .then(Task2)
 .catch(e => tag(e, "Task2"))
 .then(Task3)
 .catch(errorHandler);

不幸的是,这也会捕获来自Task1p 的错误,因此您还需要一个“catch-bypass”:

p.then(Task1)
 .then(result => Task2(result).catch(e => tag(e, "Task2")))
 .then(Task3)
 .catch(errorHandler);

这会使来自Task1 或更早版本的任何错误“绕过”(即绕过)我们对Task2 的捕获。

之所以有效,是因为我们将 catch 放在了 .then 的成功回调中。每个.then 都有一个隐含的第二个参数,它是一个错误处理程序,很像.catch,除了它没有捕获相应的成功回调,在这种情况下Task2 和我们的捕获,只有链上的先前错误(即来自Task1 或任何先前步骤的错误)。

省略一个错误回调意味着“通过它不变”。即与此处的e => Promise.reject(e) 相同。

let tag = (e, name) => (e.isFrom = name, Promise.reject(e));

let a = () => Promise.resolve();
let b = () => Promise.reject(new Error("Fail"));
let c = () => Promise.resolve();
let x = () => null.f();

let foo = failFirstStep => Promise.resolve()
.then(failFirstStep? x : a)
.then(() => b().catch(e => tag(e, "b")))
.then(c)
.catch(e => console.log((e.isFrom || "something else") + " failed"));

foo(false).then(() => foo(true));

概括地说,您可以标记函数,但我发现很难以一种惯用的方式来做,而且不会影响可读性或时间,所以我将把它作为练习。在实践中,有一个 better answer,但我发现这项技术很有趣,可以涵盖,并且在某些情况下很有用。

【讨论】:

第一个也会从p捕获错误,而不仅仅是Task1 传递0 和什么都不传递(或任何非函数参数)一样好。您应该忽略它,这是标准行为,0 会让每个阅读此代码的人感到困惑。 Protip:const tag = name => e => Promise.reject(e.tag = name, e); 会让你简单地使用.catch(tag("TaskN")) @Bergi 没错,虽然我尽量避免使用.then(foo()) 模式,因为它看起来很像一个常见的错误,但那可能只是我自己。 不,不只是你 :-) 命名可以简化它,taggingwithTagmakeTagger(或您设计的任何其他方案)可能暗示该调用确实返回另一个功能。【参考方案5】:

你可以这样做;

Promise.resolve(21).then(v => v*2)
                   .then(v => 
                                try  throw new Error(v + " is immutable")  // assuming that your code results an error
                                catch(err) return Promise.reject(message: "2nd then stage threw an error", err:err) 
                              )
                   .then(v => v)
                   .catch(err => console.log(err.message, "\n",err.err.message));

【讨论】:

Task2 被提到(我猜)作为例子,如果他们想确定错误发生在三个任务中的哪一个呢? @robertklep 如果您知道在哪个then 阶段遇到了无法控制的错误对您很重要,那么在每个fulfillment 回调中,您应该在try 块中运行您的代码并捕获就像我展示的那样,后续的 catch 块可能出现的任何错误。一旦你发现了错误,你就可以安全地返回一个包含所有必要数据和信息的拒绝承诺。 如果任务正在执行异步操作(这是公平的假设),则使用 try/catch 将不起作用。如果任务本身也使用 Promise,function Task2() return SomeTask().catch(...) 可能是更好的解决方案。 完全正确。这里的想法是不要放过错误。但是根据您的假设,如果我们在 then 阶段处理承诺的异步 API,那么正确的做法是返回收到的承诺并在下一个阶段的 fulfillment 或 @987654331 处理结果或错误@回调。这样我们总能知道错误发生在哪个阶段,也可以避免嵌套thens。所以最好在每个then 阶段都有rejected 回调(第二次回调)。 @Redu "在下一个阶段被拒绝的回调中处理错误。" 并没有真正起作用。被拒绝的回调(无论是then 还是catch)处理链中any 中的all 错误,而不仅仅是前一阶段的错误。跨度>

以上是关于我如何知道哪些处理程序在 promise 中抛出错误?的主要内容,如果未能解决你的问题,请参考以下文章

Meteor 在 promise_server.js 中抛出错误?

在Service Worker中抛出错误“Uncaught(in promise)DOMException”

android 应用程序可能出现未处理的 Promise Rejection

如何在扩展中抛出异常?

如何在 C 中抛出异常?

如何捕获ctypes中抛出的异常?