dojo 连锁承诺不传递拒绝

e5njpo68  于 2022-12-16  发布在  Dojo
关注(0)|答案(4)|浏览(137)

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

好吧,对我来说,这个结果是没有意义的。通过附加到这个承诺链,每个then都暗示着它将依赖于d1的成功解析和一个沿着链传递的结果。如果promise1中的承诺没有接收到wins值,而是在其错误处理程序中获得了一个err值,链中的下一个承诺怎么可能调用它的成功函数呢?它不可能传递一个有意义的值给下一个承诺,因为它本身没有得到一个值。
我可以用另一种方式来描述我的想法:有三个人,约翰、金吉尔和鲍勃。约翰开了一家饰品店。金吉尔来到他的店里,要了一袋各种颜色的饰品。他没有存货,所以他向他的经销商发出了一个请求,让他们把这些饰品运到他那里。与此同时,他给了Ginger一张改天的支票,说他欠她一袋小工具。Bob发现Ginger正在拿小工具,并要求他在她用完后拿蓝色的小工具。她同意了,并给了他一张纸条,说明她会的。现在,约翰的经销商找不到任何小部件在他们的供应和制造商不生产任何更多,所以他们通知约翰,谁反过来告诉金吉尔,她不能得到的小部件。鲍勃是如何能够从金吉尔得到一个蓝色的小部件时,没有得到任何自己?
我对这个问题的第三个更现实的观点是这样的。假设我有两个值要更新到数据库中。一个依赖于另一个的id,但是我不能得到id,除非我已经将它插入到数据库中并获得结果。除此之外,第一次插入依赖于来自数据库的查询。2数据库调用返回承诺,我用它把两个调用链接成一个序列。

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失败了,它将调用第一个then的err函数,但随后它将调用下一个promise的success函数,虽然该promise期待第一个值的结果,但它将从其错误处理函数获得错误消息。
所以,我的问题是,如果我必须测试成功函数中的错误,为什么还要有错误处理函数呢?
抱歉,说了这么久。我只是不知道该怎么解释。

更新和更正

(Note:我删除了我曾经对一些评论所做的回复。因此,如果有人对我的回复发表评论,他们的评论可能会在我删除后显得断章取义。抱歉,我试图尽可能简短。)
谢谢大家的回复。我想首先向大家道歉,因为我的问题写得太差了,尤其是我的伪代码。我有点过于积极地试图使它简短。
感谢Bergi的回应,我想我找到了我逻辑中的错误。我想我可能忽略了另一个导致我遇到问题的问题。这可能导致承诺链的工作方式与我想象的不同。我仍在测试我代码的不同元素,所以我甚至不能形成一个适当的问题,看看我做错了什么。我确实想更新你们所有人,但感谢你们的帮助。

pgpifvop

pgpifvop1#

对我来说,这个结果是没有意义的,通过附加到这个承诺链,每个then都暗示了它将依赖于d1的成功解析和一个结果被传递到链中的意图
不,您所描述的不是一个链,而只是将所有回调附加到d1。然而,如果您想用then链接某个东西,promise2的结果取决于promise1 * 的分辨率以及then回调如何处理它 *。
文件指出:
返回回调结果的新承诺。
.then方法通常被看作是Promises/A specification(或者更严格的Promsises/A+ one)。这意味着回调shell返回的承诺将被同化为promise2的解决方案,如果没有成功/错误处理程序,则相应的结果将直接传递到promise2-因此您可以简单地省略处理程序以传播错误。
然而,如果错误是 handled,则结果promise2被视为已修复,并将使用该值来实现。如果不希望这样,则必须re-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());

鲍勃怎么能从金吉尔那里得到一个蓝色的小部件,而她自己却没有呢?
他应该不能。如果没有错误处理程序,他只会收到消息(((来自分发者)来自约翰)来自金吉尔),没有剩余的小部件。然而,如果金吉尔为这种情况设置了错误处理程序,如果约翰或他的分发者没有剩余的蓝色小部件,她仍然可以履行承诺,从自己的小屋给予鲍勃一个绿色的小部件。
为了将错误回调转换为metapher,来自处理程序的return err就像是说“如果没有剩下的小部件,就给予他一个提示,没有剩下的小部件--这和想要的小部件一样好”。
在数据库情况下,如果db.query失败,它将调用第一个函数的err函数,然后
...这意味着错误在那里处理。如果你不这样做,就省略错误回调函数。顺便说一句,你的成功回调函数并没有实现它们所创造的承诺,所以它们看起来是相当无用的。正确的做法是:

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

xzv2uavs2#

@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,大多数其他promise库也会返回1 x rejected, 2 x resolved(这导致假设您通过使用其他promise库来获得这些结果)。
更令人困惑的是,其他库的行为更正确,让我来解释一下。
在同步世界中,“promise rejection”的对应值是throw。因此,从语义上讲,async deferred.reject(new Error()) in sync等于throw new Error()。在您的示例中,您没有在同步回调中抛出错误,您只是返回它们,因此您切换到成功流,错误值为成功值。为了确保拒绝被进一步传递,您需要重新抛出错误:

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

所以现在的问题是,为什么延迟库把返回的错误当作拒绝?
原因是deferred中的reject的工作方式有点不同,deferred lib中的规则是:promise在使用error示例解析时被拒绝,因此即使您执行deferred.resolve(new Error()),它也将充当deferred.reject(new Error()),如果您尝试执行deferred.reject(notAnError),它将抛出一个异常,说明该promise只能在使用error示例时被拒绝。这就清楚了为什么从then回调返回的error会拒绝该promise。
延迟逻辑背后有一些合理的推理,但它仍然不能与throw在JavaScript中的工作方式相提并论,因此,延迟逻辑的v0.7版本计划更改此行为。
简短总结:
为避免混淆和意外结果,请遵循良好实践规则:
1.总是用一个错误示例来拒绝你的承诺(遵循同步世界的规则,抛出不是错误的值被认为是一个坏习惯)。
1.通过抛出错误来拒绝同步回调(返回错误并不保证会被拒绝)。
遵循上述原则,您将在延迟和其他流行的promise库中获得一致和预期的结果。

aor9mmx1

aor9mmx13#

用户可以在承诺的每一层 Package 错误。我将错误链接在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
yqlxgs2m

yqlxgs2m4#

here的简单解释:
在一个常规的try..catch中,我们可以分析错误,如果它不能被处理,我们可以重新抛出它。
如果我们在.catch内抛出,则控件转到下一个最近的错误处理程序。但是如果我们处理错误并正常完成,则控件继续转到下一个最近的成功的.then处理程序。
在下面的示例中,.catch成功处理了错误:

new Promise((resolve, reject) => {

  throw new Error("Whoops!");

}).catch(function(error) {

  alert("The error is handled, continue normally");

}).then(() => alert("Next successful handler runs"));

这里catch块正常完成,所以调用下一个成功的then处理程序。
注意,我们可以拥有任意多的.then处理程序,然后在末尾使用一个.catch来处理所有这些处理程序中的错误。
如果你有中间的catch块,并且你想中断下一个链函数的错误,你应该重新抛出catch块内的错误,以表明这个错误没有被完全处理。

new Promise((resolve, reject) => {

  throw new Error("Whoops!");

}).catch(function(error) { // (*) first catch

  if (error instanceof URIError) { //just as example
    // handle it...
  } else {
    alert("Can't handle such error");
    throw error; // throwing this jumps to the next catch
  }

}).then(function() {

  // our error is other than URIError, so: 
  // the code doesn't reach here (jump to next catch)

}).catch(error => { // (**) second catch

  alert(`The unknown error has occurred: ${error}`);
  // don't return anything => execution goes the normal way

});

在上面的例子中,我们看到第一个catch()将捕获错误,但不能处理它(例如,它只知道如何处理URIError),所以它再次抛出它。执行从第一个catch()跳到链中的下一个catch(**)。

相关问题