javascript 链式承诺不会传递拒绝

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16371129/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 04:21:25  来源:igfitidea点击:

Chained promises not passing on rejection

javascriptnode.jsdojopromisedeferred

提问by Jordan

I am have a problem understanding why rejections are not passed on through a promise chain and I am hoping someone will be able to help me understand why. To me, attaching functionality to a chain of promises implies an intent that I am depending on an original promise to be fulfilled. It's hard to explain, so let me show a code example of my problem first. (Note: this example is using Node and the deferred node module. I tested this with Dojo 1.8.3 and had the same results)

我在理解为什么拒绝不通过承诺链传递时遇到问题,我希望有人能够帮助我理解原因。对我来说,将功能附加到承诺链意味着我依赖于要实现的原始承诺的意图。这很难解释,所以让我先展示我的问题的代码示例。(注意:此示例使用 Node 和延迟节点模块。我使用 Dojo 1.8.3 对此进行了测试,结果相同)

var d = require("deferred");

var d1 = d();

var promise1 = d1.promise.then(
    function(wins) { console.log('promise1 resolved'); return wins;},
    function(err) { console.log('promise1 rejected'); return err;});
var promise2 = promise1.then(
    function(wins) { console.log('promise2 resolved'); return wins;},
    function(err) { console.log('promise2 rejected'); return err;});
var promise3 = promise2.then(
    function(wins) { console.log('promise3 resolved'); return wins;},
    function(err) { console.log('promise3 rejected'); return err;});
d1.reject(new Error());

The results of running this operation is this output:

运行此操作的结果是以下输出:

promise1 rejected
promise2 resolved
promise3 resolved

Okay, to me, this result doesn't make sense. By attaching to this promise chain, each then is implying the intent that it will be dependant upon the successful resolution of d1 and a result being passed down the chain. If the promise in promise1 doesn't receive the wins value, but instead gets an err value in its error handler, how is it possible for the next promise in the chain to have its success function called? There is no way it can pass on a meaningful value to the next promise because it didn't get a value itself.

好吧,对我来说,这个结果没有意义。通过附加到这个承诺链,每一个都暗示了它将依赖于 d1 的成功解析和沿链传递的结果的意图。如果 promise1 中的 promise 没有收到 wins 值,而是在其错误处理程序中获取了 err 值,那么链中的下一个 promise 怎么可能调用其成功函数?它无法将有意义的值传递给下一个承诺,因为它本身没有获得值。

A different way I can describe what I'm thinking is: There are three people, John, Ginger, and Bob. John owns a widget shop. Ginger comes into his shop and requests a bag of widgets of assorted colours. He doesn't have them in stock, so he sends in a request to his distributor to get them shipped to him. In the mean time, he gives Ginger a rain check stating he owes her the bag of widgets. Bob finds out Ginger is getting the widgets and requests that he get the blue widget when she's done with them. She agrees and gives him a note stating she will. Now, John's distributor can't find any widgets in their supply and the manufacturer doesn't make them any more, so they inform John, who in turn informs Ginger she can't get the widgets. How is Bob able to get a blue widget from Ginger when didn't get any herself?

我可以用另一种方式来描述我的想法:有三个人,John、Ginger 和 Bob。约翰拥有一家小部件商店。Ginger 走进他的商店,要了一袋各种颜色的小工具。他没有库存,所以他向他的经销商发送请求,让他们将它们运送给他。与此同时,他给了 Ginger 一张雨票,说他欠她一袋小部件。Bob 发现 Ginger 正在获取小部件并要求他在她完成后获取蓝色小部件。她同意了,并给了他一张纸条,说明她会同意的。现在,John 的经销商在他们的供应中找不到任何小部件,制造商也不再生产这些小部件,所以他们通知 John,John 又告诉 Ginger 她无法获得这些小部件。当鲍勃自己没有得到蓝色小部件时,她怎么能从金杰那里得到一个蓝色小部件?

A third more realistic perspective I have on this issue is this. Say I have two values I want updated to a database. One is dependant on the id of the other, but I can't get the id until I have already inserted it into a database and obtained the result. On top of that, the first insert is dependant on a query from the database. The database calls return promises that I use to chain the two calls into a sequence.

我对这个问题的第三个更现实的观点是这个。假设我有两个要更新到数据库的值。一个依赖于另一个的 id,但是在我已经将它插入数据库并获得结果之前我无法获取 id。最重要的是,第一个插入依赖于来自数据库的查询。数据库调用返回我用来将两个调用链接成一个序列的承诺。

var promise = db.query({parent_id: value});
promise.then(function(query_result) {
    var first_value = {
        parent_id: query_result[0].parent_id
    }
    var promise = db.put(first_value);
    promise.then(function(first_value_result) {
        var second_value = {
            reference_to_first_value_id: first_value_result.id
        }
        var promise = db.put(second_value);
        promise.then(function(second_value_result) {
            values_successfully_entered();
        }, function(err) { return err });
    }, function(err) { return err });
}, function(err) { return err });

Now, in this situation, if the db.query failed, it would call the err function of the first then. But then it would call the success function of the next promise. While that promise is expecting the results of the first value, it would instead get the error message from its error handler function.

现在,在这种情况下,如果 db.query 失败,它将调用第一个 then 的 err 函数。但是它会调用下一个承诺的成功函数。虽然该承诺期待第一个值的结果,但它会从其错误处理函数中获取错误消息。

So, my question is, why would I have an error handing function if I have to test for errors in my success function?

所以,我的问题是,如果我必须测试成功函数中的错误,为什么我会有错误处理函数?

Sorry for the length of this. I just didn't know how to explain it another way.

对不起,这太长了。我只是不知道如何用另一种方式来解释它。

UPDATE and correction

更新和更正

(Note: I removed a response I had once made to some comments. So if anyone commented on my response, their comments might seem out of context now that I removed it. Sorry for this, I am trying to keep this as short as possible.)

(注意:我删除了我曾经对一些评论所做的回复。所以如果有人对我的回复发表评论,他们的评论在我删除后可能看起来是断章取意的。对此很抱歉,我会尽量保持简短.)

Thank you everybody who replied. I would like to first apologize to everybody for writing out my question so poorly, especially my pseudo code. I was a little too aggressive in trying to keep it short.

谢谢回复的各位。我首先向大家道歉,因为我的问题写得如此糟糕,尤其是我的伪代码。我试图保持简短,有点过于激进。

Thanks to Bergi's response, I think I found the error in my logic. I think I might have overlooked another issue that was causing the problem I was having. This is possibly causing the promise chain work differently than I thought it should. I am still testing different elements of my code, so I can't even form a proper question to see what I'm doing wrong yet. I did want to update you all though and thank you for your help.

感谢Bergi的回应,我想我发现了我的逻辑错误。我想我可能忽略了导致我遇到的问题的另一个问题。这可能导致承诺链的工作方式与我认为的不同。我仍在测试我的代码的不同元素,所以我什至无法形成一个正确的问题来看看我做错了什么。不过,我确实想为大家更新,并感谢您的帮助。

采纳答案by Bergi

To me, this result doesn't make sense. By attaching to this promise chain, each then is implying the intent that it will be dependant upon the successful resolution of d1 and a result being passed down the chain

对我来说,这个结果没有意义。通过附加到这个承诺链,每一个都暗示了它将依赖于 d1 的成功解析和沿链传递的结果的意图

No. What you are describing is not a chain, but just attaching all the callbacks to d1. Yet, if you want to chain something with then, the result for promise2is dependent on the resolution of promise1and how the thencallbacks handled it.

不。您所描述的不是链,而是将所有回调附加到d1. 然而,如果你想用 链接一些东西then,结果promise2取决于 的分辨率promise1then回调处理它的方式

The docs state:

文档状态:

Returns a new promise for the result of the callback(s).

返回回调结果的新承诺。

The .thenmethod is usually looked upon in terms of the Promises/A specification(or the even stricter Promsises/A+ one). That means the callbacks shell return promises which will be assimilated to become the resolution of promise2, and if there is no success/error handler the respective result will in case be passed directly to promise2- so you can simply omit the handlerto propagate the error.

.then通常根据Promises/A 规范(或更严格的Promises/A+规范)来看待该方法。这意味着回调 shell 返回的承诺将被同化为 的分辨率promise2,如果没有成功/错误处理程序,则相应的结果将直接传递给promise2- 因此您可以简单地省略处理程序来传播错误。

Yet, if the error is handled, the resulting promise2is seen as fixed and will be fulfilled with that value. If you don't want that, you would have to re-throwthe error, just like in a try-catch clause. Alternatively you can return a (to-be-)rejected promise from the handler. Not sure what Dojo way to reject is, but:

然而,如果错误被处理,结果promise2将被视为固定的,并将用该值实现。如果您不希望那样,您将不得不重新throw错误,就像在 try-catch 子句中一样。或者,您可以从处理程序返回一个 (to-be-)rejected promise。不确定拒绝 Dojo 的方式是什么,但是:

var d1 = d();

var promise1 = d1.promise.then(
    function(wins) { console.log('promise1 resolved'); return wins;},
    function(err) { console.log('promise1 rejected'); throw err;});
var promise2 = promise1.then(
    function(wins) { console.log('promise2 resolved'); return wins;},
    function(err) { console.log('promise2 rejected'); throw err;});
var promise3 = promise2.then(
    function(wins) { console.log('promise3 resolved'); return wins;},
    function(err) { console.log('promise3 rejected'); throw err;});
d1.reject(new Error());

How is Bob able to get a blue widget from Ginger when didn't get any herself?

当鲍勃自己没有得到蓝色小部件时,她怎么能从金杰那里得到一个蓝色小部件?

He should not be able. If there are no error handlers, he will just perceive the message (((from the distributor) from John) from Ginger) that there are no widgets left. Yet, if Ginger sets up an error handler for that case, she still might fulfill her promise to give Bob a widget by giving him a green one from her own shack if there are no blue ones left at John or his distributor.

他应该做不到。如果没有错误处理程序,他只会感知消息(((来自约翰的)来自 Ginger))没有小部件剩余。然而,如果 Ginger 为这种情况设置了一个错误处理程序,如果 John 或他的经销商处没有蓝色小部件,她仍然可以通过从她自己的小屋里给他一个绿色小部件来履行她给 Bob 一个小部件的承诺。

To translate your error callbacks into the metapher, return errfrom the handler would just be like saying "if there are no widgets left, just give him the note that there are no ones left - it's as good as the desired widget".

要将您的错误回调转换为隐喻,return err来自处理程序就像说“如果没有小部件剩余,只需给他说明没有剩余小部件 - 它与所需的小部件一样好”。

In the database situation, if the db.query failed, it would call the err function of the first then

在数据库情况下,如果 db.query 失败,它会调用 first then 的 err 函数

…which would mean that the error is handled there. If you don't do that, just omit the error callback. Btw, your success callbacks don't returnthe promises they are creating, so they seem to be quite useless. Correct would be:

...这意味着错误在那里处理。如果你不这样做,只需省略错误回调。顺便说一句,您的成功回调不是return他们正在创建的承诺,因此它们似乎毫无用处。正确的应该是:

var promise = db.query({parent_id: value});
promise.then(function(query_result) {
    var first_value = {
        parent_id: query_result[0].parent_id
    }
    var promise = db.put(first_value);
    return promise.then(function(first_value_result) {
        var second_value = {
            reference_to_first_value_id: first_value_result.id
        }
        var promise = db.put(second_value);
        return promise.then(function(second_value_result) {
            return values_successfully_entered();
        });
    });
});

or, since you don't need the closures to access result values from previous callbacks, even:

或者,由于您不需要闭包来访问先前回调的结果值,甚至:

db.query({parent_id: value}).then(function(query_result) {
    return db.put({
        parent_id: query_result[0].parent_id
    });
}).then(function(first_value_result) {
    return db.put({
        reference_to_first_value_id: first_value_result.id
    });
}.then(values_successfully_entered);

回答by Mariusz Nowak

@Jordan firstly as commenters noted, when using deferred lib, your first example definitely produces result you expect:

@Jordan 首先正如评论者指出的那样,在使用延迟库时,您的第一个示例肯定会产生您期望的结果:

promise1 rejected
promise2 rejected
promise3 rejected

Secondly, even if it would produce output you suggest, it wouldn't affect execution flow of your second snippet, which is a bit different, more like:

其次,即使它会产生您建议的输出,它也不会影响您的第二个代码段的执行流程,这有点不同,更像是:

promise.then(function(first_value) {
    console.log('promise1 resolved');
    var promise = db.put(first_value);
    promise.then(function (second_value) {
         console.log('promise2 resolved');
         var promise = db.put(second_value);
         promise.then(
             function (wins) { console.log('promise3 resolved'); },
             function (err) { console.log('promise3 rejected'); return err; });
    }, function (err) { console.log('promise2 rejected'); return err;});
}, function (err) { console.log('promise1 rejected'); return err});

and that, in case of first promise being rejected will just output:

并且,在第一个承诺被拒绝的情况下,只会输出:

promise1 rejected

However (getting to the most interesting part) even though deferred library definitely returns 3 x rejected, most of other promise libraries will return 1 x rejected, 2 x resolved(that leads to assumption you got those results by using some other promise library instead).

然而(进入最有趣的部分)即使延迟库肯定会返回3 x rejected,大多数其他承诺库也会返回1 x rejected, 2 x resolved(这导致假设您通过使用其他承诺库来获得这些结果)。

What's additionally confusing, those other libraries are more correct with their behavior. Let me explain.

更令人困惑的是,那些其他库的行为更正确。让我解释。

In a sync world counterpart of "promise rejection" is throw. So semantically, async deferred.reject(new Error())in sync equals to throw new Error(). In your example you're not throwing errors in your sync callbacks, you just returning them, therefore you switch to success flow, with an error being a success value. To make sure rejection is passed further, you need to re-throw your errors:

在同步世界中,“承诺拒绝”的对应物是throw. 所以在语义上,异步deferred.reject(new Error())同步等于throw new Error(). 在您的示例中,您不会在同步回调中​​抛出错误,而只是返回它们,因此您切换到成功流程,错误是成功值。为了确保拒绝被进一步传递,您需要重新抛出您的错误:

function (err) { console.log('promise1 rejected'); throw err; });

So now question is, why do deferred library took returned error as rejection?

那么现在的问题是,为什么延迟库将返回的错误视为拒绝?

Reason for that, is that rejection in deferred works a bit different. In deferred lib the rule is: promise is rejected when it's resolved with an instance of error, so even if you do deferred.resolve(new Error())it will act as deferred.reject(new Error()), and if you try to do deferred.reject(notAnError)it will throw an exception saying, that promise can be rejected only with instance of error. That makes clear why error returned from thencallback rejects the promise.

原因是延迟中的拒绝工作有点不同。在 deferred lib 中,规则是:当它通过一个 error 实例解决时,promise 被拒绝,所以即使你这样做了,deferred.resolve(new Error())它也会作为deferred.reject(new Error()),如果你尝试这样做deferred.reject(notAnError)会抛出一个异常,说这个承诺只能被实例拒绝的错误。这清楚地说明了为什么从then回调返回的错误拒绝了承诺。

There is some valid reasoning behind deferred logic, but still it's not on par with how throwworks in JavaScript, and due to that this behavior is scheduled for change with version v0.7 of deferred.

deferred 逻辑背后​​有一些有效的推理,但它仍然与throwJavaScript 中的工作方式不符,因此这种行为计划在 deferred 的 v0.7 版本中进行更改。

Short summary:

简短的摘要:

To avoid confusion and unexpected results just follow the good practice rules:

为避免混淆和意外结果,只需遵循良好实践规则:

  1. Always reject your promises with an error instances (follow rules of sync world, where throwing value that's not an error is considered a bad practice).
  2. Reject from sync callbacks by throwingerrors (returning them doesn't guarantee rejection).
  1. 总是用错误实例拒绝你的承诺(遵循同步世界的规则,抛出不是错误的值被认为是一种不好的做法)。
  2. 通过抛出错误拒绝同步回调(返回它们并不能保证拒绝)。

Obeying to above, you'll get both consistent and expected results in both deferred and other popular promise libraries.

遵守上述规定,您将在延迟和其他流行的承诺库中获得一致和预期的结果。

回答by Mathew Kurian

Use can wrap the errors at each level of the Promise. I chained the errors in TraceError:

使用可以包装 Promise 各个级别的错误。我链接了TraceError 中的错误:

class TraceError extends Error {
  constructor(message, ...causes) {
    super(message);

    const stack = Object.getOwnPropertyDescriptor(this, 'stack');

    Object.defineProperty(this, 'stack', {
      get: () => {
        const stacktrace = stack.get.call(this);
        let causeStacktrace = '';

        for (const cause of causes) {
          if (cause.sourceStack) { // trigger lookup
            causeStacktrace += `\n${cause.sourceStack}`;
          } else if (cause instanceof Error) {
            causeStacktrace += `\n${cause.stack}`;
          } else {
            try {
              const json = JSON.stringify(cause, null, 2);
              causeStacktrace += `\n${json.split('\n').join('\n    ')}`;
            } catch (e) {
              causeStacktrace += `\n${cause}`;
              // ignore
            }
          }
        }

        causeStacktrace = causeStacktrace.split('\n').join('\n    ');

        return stacktrace + causeStacktrace;
      }
    });

    // access first error
    Object.defineProperty(this, 'cause', {value: () => causes[0], enumerable: false, writable: false});

    // untested; access cause stack with error.causes()
    Object.defineProperty(this, 'causes', {value: () => causes, enumerable: false, writable: false});
  }
}

Usage

用法

throw new TraceError('Could not set status', srcError, ...otherErrors);

Output

输出

Functions

职能

TraceError#cause - first error
TraceError#causes - list of chained errors