打破承诺链并根据链中的步骤调用一个函数(它被拒绝)

m59*_*m59 129 javascript promise angularjs

更新:

为了帮助这篇文章的未来观众,我创建了这个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); 
  }
Run Code Online (Sandbox Code Playgroud)

这里的问题是,如果我在第1步失败,两个stepError(1)AND都会stepError(2)被解雇.如果我不return $q.reject那么stepError(2)就不会被解雇,但step(2)会,我明白了.除了我想做的事以外,我已经完成了所有的事情.

如何编写promises以便我可以在拒绝时调用函数,而无需调用错误链中的所有函数?或者还有另一种方法来实现这一目标吗?

这是一个现场演示,所以你有一些工作.

更新:

有点解决了.在这里,我在链的末尾捕获错误并传递数据,reject(data)以便我知道错误函数中要处理的问题.这实际上不符合我的要求,因为我不想依赖于数据.它会很蹩脚,但在我的情况下,将错误回调传递给函数会更加清晰,而不是依赖于返回的数据来确定要做什么.

现场演示(点击).

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); 
  }
Run Code Online (Sandbox Code Playgroud)

Ala*_*lum 186

你的代码没有按预期工作的原因是它实际上做的与你的想法不同.

假设您有以下内容:

stepOne()
.then(stepTwo, handleErrorOne)
.then(stepThree, handleErrorTwo)
.then(null, handleErrorThree);
Run Code Online (Sandbox Code Playgroud)

为了更好地理解发生了什么,让我们假装这是带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);
}
Run Code Online (Sandbox Code Playgroud)

onRejected处理程序(的第二个参数then)本质上是一个纠错机制(如catch块).如果抛出错误handleErrorOne,它将被下一个catch块(catch(e2))捕获,依此类推.

这显然不是你想要的.

假设我们希望整个解决方案链无论出现什么问题都会失败:

stepOne()
.then(function(a) {
    return stepTwo(a).then(null, handleErrorTwo);
}, handleErrorOne)
.then(function(b) {
    return stepThree(b).then(null, handleErrorThree);
});
Run Code Online (Sandbox Code Playgroud)

注意:我们可以保留handleErrorOne它所在的位置,因为它只会在stepOne拒绝时被调用(它是链中的第一个函数,所以我们知道如果此时链被拒绝,那只能是因为该函数的承诺) .

重要的变化是其他函数的错误处理程序不是主要承诺链的一部分.相反,每个步骤都有自己的"子链",onRejected只有在步骤被拒绝时才会被调用(但主链不能直接到达).

这部作品的原因是,无论onFulfilledonRejected可选参数的then方法.如果一个promise被实现(即已解决)并且then链中的下一个没有onFulfilled处理程序,则链将继续,直到有一个具有这样的处理程序.

这意味着以下两行是等效的:

stepOne().then(stepTwo, handleErrorOne)
stepOne().then(null, handleErrorOne).then(stepTwo)
Run Code Online (Sandbox Code Playgroud)

但以下行等于上述两行:

stepOne().then(stepTwo).then(null, handleErrorOne)
Run Code Online (Sandbox Code Playgroud)

Angular的promise库$q基于kriskowal的Q库(它具有更丰富的API,但包含您可以找到的所有内容$q).Q 在GitHub上的API文档可能会很有用.Q实现了Promises/A +规范,详细说明了如何then和promise解析行为完全正常工作.

编辑:

还要记住,如果你想在错误处理程序中突破链,它需要返回一个被拒绝的promise或抛出一个错误(它将被捕获并自动包含在被拒绝的promise中).如果您未返回承诺,then请将退货值包装在您的解决承诺中.

这意味着如果您不返回任何内容,则您实际上会返回已解决的值的承诺undefined.

  • 这部分是黄金:`如果你没有返回任何东西,你实际上是返回一个已解决的未定义值的承诺.谢谢@pluma (128认同)
  • 确实如此.我正在编辑它以给予它应得的大胆 (7认同)
  • 对于提出的问题,并没有真正提供明确的解决方案,但仍然有很好的解释 (4认同)

Vin*_*q12 51

派对迟到了,但这个简单的解决方案对我有用:

function chainError(err) {
  return Promise.reject(err)
};

stepOne()
.then(stepTwo, chainError)
.then(stepThreee, chainError);
Run Code Online (Sandbox Code Playgroud)

这可以让你突破链条.

  • 帮助了我,但仅供参考,您可以在 then 中返回它以在捕获中突围:`.then(user => { if (user) return Promise.reject('The email address already exists.') })` (2认同)
  • @CraigvanTonder,你可以在一个承诺中添加它,它的工作方式与你的代码相同: `.then(user => { if (user) throw '电子邮件地址已存在。' }) ` (2认同)
  • 这是唯一正确的答案。否则即使步骤 1 出错,步骤 3 仍然会执行。 (2认同)
  • 只是为了澄清一下,如果 stepOne() 中发生错误,那么 chainError 都会被调用,对吧?如果这是可取的。我有一个片段可以做到这一点,不确定我是否误解了什么 - https://runkit.com/embed/9q2q3rjxdar9 (2认同)

Bee*_*oot 9

你需要的是一个重复的.then()链条,一个特殊的案例开始和一个特殊的案例来完成.

诀窍是让故障情况的步骤编号波及到最终的错误处理程序.

  • 开始:step(1)无条件呼叫.
  • 重复模式:.then()使用以下回调链a :
    • 成功:呼叫步骤(n + 1)
    • 失败:抛出前一个被拒绝的值或重新抛出错误.
  • 完成:链接a .then()没有成功处理程序和最终错误处理程序.

你可以用手写出整个东西,但用命名的通用函数来演示模式更容易:

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);});
Run Code Online (Sandbox Code Playgroud)

看看演示

注意如何step()拒绝或解决延迟n,从而使该值可用于.then()链中下一个的回调.一旦stepError被调用,错误将被反复重新抛出,直到被处理为止finalError.


red*_*ben 6

在拒绝时,您应该传递拒绝错误,然后将步骤错误处理程序包装在一个函数中,该函数检查是否应该处理拒绝或"重新抛出"直到链的末尾:

// 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);
  });
Run Code Online (Sandbox Code Playgroud)

您在控制台上看到的内容:

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
Run Code Online (Sandbox Code Playgroud)

这是一些工作代码 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);    
    }
}
Run Code Online (Sandbox Code Playgroud)

那你的连锁店

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);
});
Run Code Online (Sandbox Code Playgroud)