打破诺言链,并根据链中断的步骤调用函数(被拒绝)
更新:
为了帮助这篇文章的未来观众,我创build了pluma答案的演示 。
题:
我的目标看起来相当简单。
step(1) .then(function() { return step(2); }, function() { stepError(1); return $q.reject(); }) .then(function() { }, function() { stepError(2); }); function step(n) { var deferred = $q.defer(); //fail on step 1 (n === 1) ? deferred.reject() : deferred.resolve(); return deferred.promise; } function stepError(n) { console.log(n); }
这里的问题是,如果我在第一步失败, stepError(1)
和stepError(2)
。 如果我不return $q.reject
那么stepError(2)
将不会被触发,但是step(2)
将会被理解。 除了我想要做的,我已经完成了一切。
我如何写承诺,以便我可以调用拒绝函数,而不调用错误链中的所有函数? 还是有另一种方法来完成这个?
这里有一个现场演示,所以你有一些工作。
更新:
我有种解决了它。 在这里,我捕捉链末尾的错误并传递数据来reject(data)
以便我知道在错误函数中处理什么问题。 这实际上不符合我的要求,因为我不想依赖数据。 这将是跛脚,但在我的情况下,将错误callback传递给该函数更清洁,而不是依赖返回的数据来确定要做什么。
现场演示(单击)。
step(1) .then(function() { return step(2); }) .then(function() { return step(3); }) .then(false, function(x) { stepError(x); } ); function step(n) { console.log('Step '+n); var deferred = $q.defer(); (n === 1) ? deferred.reject(n) : deferred.resolve(n); return deferred.promise; } function stepError(n) { console.log('Error '+n); }
你的代码不能按预期工作的原因是它实际上正在做一些与你想象的不同的事情。
假设你有如下的东西:
stepOne() .then(stepTwo, handleErrorOne) .then(stepThree, handleErrorTwo) .then(null, handleErrorThree);
为了更好地理解发生了什么,让我们假设这是与try
/ catch
块同步的代码:
try { try { try { var a = stepOne(); } catch(e1) { a = handleErrorOne(e1); } var b = stepTwo(a); } catch(e2) { b = handleErrorTwo(e2); } var c = stepThree(b); } catch(e3) { c = handleErrorThree(e3); }
onRejected
处理程序(当时的第二个参数)基本上是一个错误纠正机制(就像一个catch
块)。 如果在handleErrorOne
抛出错误,它将被下一个catch块catch(e2)
),依此类推。
这显然不是你想要的。
假设我们希望整个解决scheme链无论出现什么问题都会失败:
stepOne() .then(function(a) { return stepTwo(a).then(null, handleErrorTwo); }, handleErrorOne) .then(function(b) { return stepThree(b).then(null, handleErrorThree); });
注意:我们可以把handleErrorOne
放在原来的位置,因为只有当stepOne
拒绝时才会调用它(这是链中的第一个函数,所以我们知道如果链在这个时候被拒绝,只能是因为函数的诺言)。
重要的变化是其他函数的error handling程序不是主要承诺链的一部分。 相反,每个步骤都有自己的“子链”,只有在步骤被拒绝(但不能直接到达主链)时才会调用onRejected
。
这个工作的原因是onFulfilled
和onRejected
都是then
方法的可选参数。 如果一个承诺履行(即解决), then
在链中没有一个onFulfilled
处理程序,链将继续,直到有一个这样的处理程序。
这意味着以下两行是等同的:
stepOne().then(stepTwo, handleErrorOne) stepOne().then(null, handleErrorOne).then(stepTwo)
但是下面这行不等于上面两行:
stepOne().then(stepTwo).then(null, handleErrorOne)
Angular的承诺库$q
是基于kriskowal的Q
库(它有一个更丰富的API,但包含了你可以在$q
find的所有东西)。 Q在GitHub上的API文档可能是有用的。 Q实现了Promises / A +规范 ,详细说明了如何以及承诺parsing行为是如何工作的。
编辑:
另外请记住,如果您想在error handling程序中跳出链,那么它需要返回一个被拒绝的承诺或抛出一个错误(它将被自动捕获并封装在被拒绝的承诺中)。 如果你没有返回一个承诺, then
将返回值包装在一个解决的承诺中。
这意味着如果你不返回任何东西,你实际上是返回一个undefined
的值。
比赛晚了,但这个简单的解决scheme为我工作:
function chainError(err) { return Promise.reject(err) }; stepOne() .then(stepTwo, chainError) .then(stepThreee, chainError);
这可以让你摆脱链条。
你需要的是一个重复的.then()
链,一个特殊的情况下开始和一个特殊的情况下完成。
诀窍是获取失败案例的步骤号码,以涟漪通过最终的error handling程序。
- 开始:无条件地调用
step(1)
。 - 重复模式:使用以下callback链接一个
.then()
:- 成功:调用步骤(n + 1)
- 失败:抛出之前被拒绝的值或重新抛出错误。
- 完成:链接
.then()
没有成功处理程序和最终的error handling程序。
你可以把所有东西都写出来,但是用命名的泛化函数来演示模式更容易:
function nextStep(n) { return step(n + 1); } function step(n) { console.log('step ' + n); var deferred = $q.defer(); (n === 3) ? deferred.reject(n) : deferred.resolve(n); return deferred.promise; } function stepError(n) { throw(n); } function finalError(n) { console.log('finalError ' + n); } step(1) .then(nextStep, stepError) .then(nextStep, stepError) .then(nextStep, stepError) .then(nextStep, stepError) .then(nextStep, stepError) .then(null, finalError);});
看演示
请注意,在step()
,延迟被拒绝或用n
parsing,从而使该值可用于链中下一个.then()
中的callback。 一旦调用了stepError
,错误就会重复出现,直到被finalError
处理。
当拒绝你应该通过一个拒绝错误,然后包装步骤error handling程序的function,检查是否应该处理拒绝或“rethrown”,直到链的末尾:
// function mocking steps function step(i) { i++; console.log('step', i); return q.resolve(i); } // function mocking a failing step function failingStep(i) { i++; console.log('step '+ i + ' (will fail)'); var e = new Error('Failed on step ' + i); e.step = i; return q.reject(e); } // error handler function handleError(e){ if (error.breakChain) { // handleError has already been called on this error // (see code bellow) log('errorHandler: skip handling'); return q.reject(error); } // firs time this error is past to the handler console.error('errorHandler: caught error ' + error.message); // process the error // ... // error.breakChain = true; return q.reject(error); } // run the steps, will fail on step 4 // and not run step 5 and 6 // note that handleError of step 5 will be called // but since we use that error.breakChain boolean // no processing will happen and the error will // continue through the rejection path until done(,) step(0) // 1 .catch(handleError) .then(step) // 2 .catch(handleError) .then(step) // 3 .catch(handleError) .then(failingStep) // 4 fail .catch(handleError) .then(step) // 5 .catch(handleError) .then(step) // 6 .catch(handleError) .done(function(){ log('success arguments', arguments); }, function (error) { log('Done, chain broke at step ' + error.step); });
你会在控制台上看到什么:
step 1 step 2 step 3 step 4 (will fail) errorHandler: caught error 'Failed on step 4' errorHandler: skip handling errorHandler: skip handling Done, chain broke at step 4
这是一些工作代码https://jsfiddle.net/8hzg5s7m/3/
如果你对每一步有特定的处理,你的包装可能是这样的:
/* * simple wrapper to check if rejection * has already been handled * @param function real error handler */ function createHandler(realHandler) { return function(error) { if (error.breakChain) { return q.reject(error); } realHandler(error); error.breakChain = true; return q.reject(error); } }
那么你的链条
step1() .catch(createHandler(handleError1Fn)) .then(step2) .catch(createHandler(handleError2Fn)) .then(step3) .catch(createHandler(handleError3Fn)) .done(function(){ log('success'); }, function (error) { log('Done, chain broke at step ' + error.step); });
如果我理解正确,你只想要失败的步骤显示的错误,对不对?
这应该像改变第一个承诺的失败案例一样简单:
step(1).then(function (response) { step(2); }, function (response) { stepError(1); return response; }).then( ... )
通过在第一步失败的情况下返回$q.reject()
,你拒绝了这个承诺,这将导致在第二个then(...)
调用errorCallback。
var s = 1; start() .then(function(){ return step(s++); }) .then(function() { return step(s++); }) .then(function() { return step(s++); }) .then(0, function(e){ console.log(s-1); });
http://jsbin.com/EpaZIsIp/20/edit
或者自动执行任何步骤:
var promise = start(); var s = 1; var l = 3; while(l--) { promise = promise.then(function() { return step(s++); }); } promise.then(0, function(e){ console.log(s-1); });
将error handling程序作为单独的链接元素直接附加到执行步骤:
// Handle errors for step(1) step(1).then(null, function() { stepError(1); return $q.reject(); }) .then(function() { // Attach error handler for step(2), // but only if step(2) is actually executed return step(2).then(null, function() { stepError(2); return $q.reject(); }); }) .then(function() { // Attach error handler for step(3), // but only if step(3) is actually executed return step(3).then(null, function() { stepError(3); return $q.reject(); }); });
或使用catch()
:
// Handle errors for step(1) step(1).catch(function() { stepError(1); return $q.reject(); }) .then(function() { // Attach error handler for step(2), // but only if step(2) is actually executed return step(2).catch(function() { stepError(2); return $q.reject(); }); }) .then(function() { // Attach error handler for step(3), // but only if step(3) is actually executed return step(3).catch(function() { stepError(3); return $q.reject(); }); });
注意:这与pluma在答案中提出的基本相同的模式,但使用OP的命名。