使用errorCallback中断Promise“then”链

9

-- 编辑 --

最近我遇到了一个关于 Promise 的奇怪问题,但我猜这可能是因为它违背了 Promise 的哲学。

考虑以下代码:

// Assuming Auth is just a simple lib doing http requests with promises
Auth.signup()
 .then(succCall, errCall)
 .then(loginSucc, loginErr)

// My callbacks here
function succCall (){
 // OK, send second promise
 console.log('succCall');
 return Auth.login();
}

function errCall(){
 // I do some things here and now
 // I want to break out from here
 console.log('errCall');
}

function loginSucc(){
 // This is the callback of the login method when it went OK
 // I want to enter here ONLY if with go through the succCall
 console.log('loginSucc');
}

function loginErr(){
 // This is the callback of the login method when it went not ok
 // I want to enter here ONLY if with go through the succCall
 console.log('loginErr');
}

如果Auth.signup()出现问题,会显示以下内容:

  • errCall,loginSucc

如果在errCall中执行$q.reject(),下面是发生的情况:

  • errCall,loginErr

这就是我想要的结果:

  • errCall... 结束,停在这里

现在的问题是,当注册出错时,它进入了errCall,这很好,但接下来它进入了loginSucc...

当任何错误回调函数(在此为errCall或loginErr)被遇到时,我想要跳出“then”链。

--编辑--

我觉得有些人误解了我的意思,我想完全中断链,不希望在任何其他“then”中检查是否出现问题。

就像我说的:如果第一个“then”出现问题,停在这里;如果第一个“then”正常,则继续;如果第二个“then”正常,则继续;如果第三个“then”出现问题,则停止。

// Just like if i did the following but by chainning "then" methods
// My callbacks here
function succCall (){
 // OK, send second promise
 return Auth.login().then(loginSucc, loginErr);
}

我的意思是,如果我有许多“then”链接,我不希望只有一个错误处理程序。

@SoluableNonagon的回答是正确的 - 使用catch跳过您的errCallloginErr函数,并将它们的逻辑放在catch中。您将不得不查看catch回调捕获的错误,但这是实现您想要的唯一方法。 - Adam Jenkins
你能否将其分为两部分?Auth.signup().then(succCall, errCall)succCall.then(loginSucc, loginErr),类似这样的。 - koox00
理想情况下,这就是我想要的,但如何分解它呢?@koox00 - darkylmnx
你唯一的选择是返回一个永远不会被解决的 Promise,而不是在第一个错误处理程序中拒绝它。请参见下面我更新后的答案。 - lex82
你试图实现的东西被认为是一种反模式。一个链应该理想地是一个单一的成功场景,有一个捕获来处理潜在的错误。如果你想在链中做更多的分支,它很容易变成一个维护噩梦。问问自己为什么要处理中间错误。你想记录一些东西吗?只需使用特定的错误消息。你想清理吗?使用特定的错误类型。你想做一些高级操作吗?将链分成几个部分。 - Vincent van der Weele
显示剩余3条评论
4个回答

2

errCall函数需要返回一个Promise对象,并且该Promise对象需要被拒绝(rejected)才能触发loginErr。

function errCall(){
   // i do some things here and now

   return $q(function(resolve, reject) {
        // auto reject
        reject();
   });


}

或者尝试使用 .catch:

Auth.signup()
 .then(succCall)
 .then(loginSucc)
 .catch(function(err){
      // caught error, problem is you won't know which function errored out, so you'll need to look at the error response
 });

这正是我不想要的,如果进入 errCall,我不希望有任何操作被执行,所有其他操作都应该被忽略,就像基本回调语法中一样。第二个 then 对我来说是 succCall 的回调,而不是 errCall,这是关键点。 - darkylmnx

2
有效地发生的是这样的事情:
    try {
        try {
            var a = succCall();
        } catch(e1) {
            a = errCall(e1);
        }
        var b = loginSucc(a);
    } catch(e2) {
        b = loginErr(e2);
    }

你可以通过调用以下方法来打破这个链:
return $q.reject('Reason Err was called');

在你的errCall()函数中。 编辑: 正如OP所指出的,通过调用$q.reject,代码将进入loginErr函数。 或者您可以按照以下方式修改您的代码:
Auth.signup()
.then(function(a) {
    succCall()
    return loginSucc(a).then(null, loginErr);
}, errCall)

您可以在以下两个SO问题中了解更多:

  1. 如何打破Promise链并根据链中的步骤调用函数
  2. 如何在Angularjs中跳出then Promise

这篇文章也很有帮助:平铺Promise链


你的答案并不准确,因为它没有打破链条,而是转到第二个错误,这不是我想要的。当遇到任何errorCallback(在这里是errCall或loginErr)时,我想要跳出then链。 - darkylmnx
是的,这就是我不想要的,开始一个金字塔式的承诺模式,但我猜没有像循环或switch语法中那样打破承诺方法。 - darkylmnx
我没有找到一个好的方法,不是用goto - 我开玩笑的! - gyosifov
如果两种情况都会向用户呈现消息,则使用.catch与.then错误过滤器结合使用更有意义。让.then将错误转换为要呈现的消息,然后在.catch中呈现它,这样代码就更简洁了。renderError(message) - Kevin B
这取决于它们是否都执行x,或者每个结果完全不同。通常呈现错误消息是相当标准的,不需要if else逻辑。 - Kevin B
显示剩余3条评论

1

只需不将任何errCallloginErr传递给then,并在承诺链的末尾使用catch(),它将在第一个错误上中断,并将传递给catch()。如果您想明确处理Auth.signup()的错误,则errCall应如下所示:

function (err) {
  if(isFatal(err)) {
    return Promise.reject(new Error('Fatal!')); //`catch()` handler will be called with `new Error('Fatal!')`
  } else {
    return 'something'; //next `then()` handler will be called with 'something'
  }
}

这意味着我必须在每个成功或解决回调中检查一个值,以检查前一个是否失败,我觉得这样做相当丑陋。 - darkylmnx
不,你不必检查每个回调值,除非你想显式地处理特定的Promise失败结果。 - Zuker
你的方法不能让我单独处理每个错误,这正是我所寻求的,但我已经找到了解决方案,谢谢。 - darkylmnx

0

你最好的选择是从errCall()返回一个永远不会被解决的Promise:

function errCall() {
     console.log('errCall');
     return $q.defer().promise;
}

但是你说得对,你试图做的事情“违背了Promise的哲学原则”。

为什么你不应该这样做

当第一个Promise的评估出现错误时,调用loginSucc函数当然是个问题。然而,正如其他人已经指出的那样,在errCall中再次拒绝即可解决此问题。但是你不应该试图避免执行loginErr,否则你的代码在概念上存在问题。

表达式的评估

Auth.signup().then(succCall, errCall)

返回另一个Promise。合同是Promise具有一个then()方法,该方法接受两个回调作为参数,一个用于成功,一个用于失败。合同还规定,当成功评估Promise时,将调用成功回调,并且在所有其他情况下调用错误/失败回调。如果您打算从未调用它们中的任何一个,请不要使用Promise!


你说的有些不对,我想要运行其中一个:Auth.signup().then(succCall, errCall),但我不想要下一个 "then" Auth.signup().then(succCall, errCall).then(loginSucc, loginErr) 被执行,因为前一个被拒绝了。这就是我想要实现的,但是可能违背 Promise 的哲学。 - darkylmnx
我理解你的问题。我更新了我的答案,所以现在希望清楚我想表达什么。 - lex82
谢谢,我会尝试这个方法。我发现执行 $q(function(){return null;}) 也可以取消 Promise 的传递。 - darkylmnx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接