链式承诺不会传递拒绝

时间:2013-05-04 06:05:58

标签: javascript node.js dojo promise deferred

我有一个问题,理解为什么拒绝不通过承诺链传递,我希望有人能够帮助我理解为什么。对我而言,将功能附加到承诺链意味着我依赖于原始承诺的意图。这很难解释,所以让我先展示一下我的问题的代码示例。 (注意:这个例子是使用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());

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

promise1 rejected
promise2 resolved
promise3 resolved

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

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

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

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 });

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

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

对不起这件事的长度。我只是不知道如何以另一种方式解释它。

更新和更正

(注意:我删除了我曾经对某些评论做出的回复。所以,如果有人对我的回复发表评论,那么他们的评论可能看起来不合时宜,因为我删除了它。对不起,我试图将此保留为尽可能短。)

谢谢大家的回复。我想首先向所有人道歉,因为我的问题写得太差了,特别是我的伪代码。试图保持简短,我有点过于激进了。

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

3 个答案:

答案 0 :(得分:26)

  

对我来说,这个结果没有意义。通过附加到这个承诺链,每个意味着它意味着它将依赖于d1的成功解决和结果传递到链

没有。您所描述的不是链,而是将所有回调附加到d1。但是,如果您希望使用then链接某些内容,则promise2的结果取决于promise1 的分辨率以及then回调如何处理它

文档声明:

  

返回回调结果的新承诺。

.then方法通常根据Promises/A specification(或更严格的Promsises/A+ one)进行查看。这意味着回调shell返回将被同化为promise2的解析的promises,如果没有成功/错误处理程序,相应的结果将直接传递给promise2 - 所以你可以只需省略处理程序即可传播错误。

然而,如果错误是处理,则生成的promise2被视为已修复,并将使用该值来实现。如果您不想这样,则必须重新throw错误,就像在try-catch子句中一样。或者,您可以从处理程序返回(待)拒绝的承诺。不确定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());
  

如果没有得到任何自己,鲍勃如何能够从Ginger获得蓝色小部件?

他不应该。如果没有错误处理程序,他只会感知消息((来自分销商)来自约翰)来自Ginger)没有小部件。然而,如果Ginger为那个案子设置了一个错误处理程序,她仍然可以履行她的承诺,如果John或他的经销商没有留下蓝色的那个,就给他一个绿色的小屋,给Bob一个小部件。

要将错误回调转换为metapher,来自处理程序的return err就像是在说“如果没有小部件,只要告诉他没有剩下的小部件 - 它就像所需的小部件“。

  

在数据库情况下,如果db.query失败,它会调用第一个的错误函数

...这意味着在那里处理错误。如果不这样做,只需省略错误回调即可。顺便说一下,你的成功回调不会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();
        });
    });
});

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

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);

答案 1 :(得分:1)

@Jordan首先作为评论者指出,当使用deferred lib时,你的第一个例子肯定会产生你期望的结果:

promise1 rejected
promise2 rejected
promise3 rejected

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

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});

并且,如果第一个承诺被拒绝,则只输出:

promise1 rejected

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

另外令人困惑的是,其他图书馆的行为更为正确。让我解释一下。

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

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

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

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

延迟逻辑背后有一些有效的推理,但它仍然不能与throw在JavaScript中的工作方式相提并论,并且由于此行为计划在延迟版本v0.7中进行更改

简短摘要:

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

  1. 始终拒绝您的承诺与错误实例(遵循同步世界规则,抛出不是错误的价值被视为不良做法)。
  2. 通过投掷错误拒绝来自同步回调(返回它们并不能保证拒绝)。
  3. 遵守上述规则,您将在延期和其他受欢迎的承诺库中获得一致和预期的结果。

答案 2 :(得分:0)

使用可以在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});
  }
}

<强>用法

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

<强>输出

<强>功能

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