连锁的承诺没有通过拒绝

我有一个问题,理解为什么拒绝不通过承诺链传递,我希望有人能够帮助我理解为什么。 对我来说,将function附加到一系列的承诺意味着我的意图是我依靠一个原始的承诺来实现。 这很难解释,所以让我首先展示一个我的问题的代码示例。 (注意:这个例子是使用Node和延迟节点模块,我用Dojo 1.8.3testing过,结果相同)

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中的承诺没有收到wins值,而是在其error handling程序中获得错误值,那么链中的下一个承诺如何能够调用其成功函数呢? 它没有办法将一个有意义的价值传递给下一个承诺,因为它本身并没有获得价值。

我可以用不同的方式描述我在想什么:有三个人,约翰,姜和鲍勃。 约翰拥有一个小工具店。 姜进入他的商店,并要求一袋各种颜色的小工具。 他没有库存,所以他发送一个请求给他的经销商,让他们运到他身边。 与此同时,他给了姜一个雨支票,说他欠她的小工具包。 鲍勃发现姜正在拿到小部件,并要求他拿到蓝色小部件。 她同意,并给他一个说明,她会的。 现在,约翰的经销商找不到任何小配件,制造商也不再制造这些小配件,所以他们告诉约翰,约翰反过来告诉生姜她不能拿到小配件。 当鲍勃从没有得到任何东西的时候,鲍勃如何能够从姜尔那里得到一个蓝色的小部件?

我在这个问题上的第三个更现实的观点是这样的。 说我有两个值我想更新到一个数据库。 一个是依赖于另一个的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 }); 

现在,在这种情况下,如果db.query失败,那么会调用第一个的err函数。 但是接下来呢就是下一个承诺的成功function。 虽然这个承诺是期待第一个值的结果,它会从它的error handling函数获取错误信息。

所以,我的问题是,如果我必须testing我的成功函数中的错误,为什么会出现error handling函数?

对不起,这个长度。 我只是不知道如何解释另一种方式。

更新和更正

(注意:我删除了一些有关评论的回复,因此如果有人对我的回复发表评论,那么他们的评论可能显得与上下文无关,因此我已经将其删除了,对此,我尽量保持尽可能短。)

谢谢大家回复。 我想首先向大家道歉,写出我的问题,特别是我的伪代码。 为了保持简短,我有些过于激进。

感谢Bergi的回应,我想我发现了我的逻辑错误。 我想我可能忽略了导致我遇到的问题的另一个问题。 这可能导致承诺链工作不同于我认为应该。 我仍然在testing我的代码的不同元素,所以我甚至不能形成一个正确的问题,看看我做错了什么。 我确实希望尽快更新,谢谢你的帮助。

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

不,你所描述的不是一个链,而是把所有的callback附加到d1 。 然而,如果你想用某种方式来链接某个东西, then promise2的结果将取决于promise2的parsing以及thencallback如何处理它

文档状态:

返回callback结果的新承诺。

.then方法通常以Promises / A规范 (或更严格的Promsises / A +之一 )来看待 。 这意味着callbackshell返回承诺,将被同promise2 ,如果没有成功/error handling程序,相应的结果将直接传递给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()); 

当鲍勃从没有得到任何东西的时候,鲍勃如何能够从姜尔那里得到一个蓝色的小部件?

他不应该能够。 如果没有error handling程序,他只会从Ginger中((来自John的分销商))看到没有剩下的小部件。 然而,如果姜格为这种情况设置了一个error handling程序,她仍然可以履行自己的承诺,如果在约翰或者他的经销商那里没有留下蓝色的东西,她就会给自己一个绿色的东西给鲍勃一个小工具。

要将你的错误callback翻译成metapher,从处理程序return err就好像在说:“如果没有widgets,只要给他留下没有剩下的东西 – 它和所需的部件一样好”。

在数据库情况下,如果db.query失败了,那么会调用第一个的err函数

…这意味着错误在那里处理。 如果你不这样做,只是省略错误callback。 顺便说一句,你的成功callback不会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(); }); }); }); 

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

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

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

 promise1 rejected promise2 rejected promise3 rejected 

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

 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 (这导致假设你通过使用一些其他的承诺库来获得这些结果) 。

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

在“诺言拒绝”的同步世界对手是throw 。 所以在语义上,async deferred.reject(new Error())同步等于throw new Error() 。 在你的例子中,你不会在你的同步callback中​​抛出错误,你只是返回它们,所以你切换到成功的stream程,错误是成功的价值。 为了确保拒绝得到进一步的通过,你需要重新抛出你的错误:

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

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

原因是,延期的拒绝工作有点不同。 在延迟的lib中,规则是: promise在被错误实例parsing时被拒绝 ,所以即使你做了deferred.resolve(new Error())它也会作为deferred.reject(new Error()) ,如果你尝试做deferred.reject(notAnError)它会抛出一个exception说,这个承诺可以拒绝只有错误的实例。 这就明白了为什么从callback返回的错误拒绝了承诺。

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

简短的摘要:

为了避免混淆和意想不到的结果,请按照良好的实践规则:

  1. 总是拒绝你的承诺与一个错误的情况下(遵循同步世界的规则,抛出值不是一个错误被认为是一个不好的做法)。
  2. 通过抛出错误拒绝同步callback(返回它们并不能保证拒绝)。

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

使用可以包装承诺的每个级别的错误。 我链接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); 

产量

function

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