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

IT技术 javascript angularjs promise
2021-02-07 17:10:34

更新:

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

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

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

这是一个现场演示,因此您可以使用一些东西。

更新:

有种已经解决了它。在这里,我在链的末尾捕获错误并将数据传递给,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); 
  }
6个回答

您的代码没有按预期工作的原因是它实际上在做与您认为的不同的事情。

假设您有以下内容:

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处理程序(的第二个参数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);
});

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

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

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

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

stepOne().then(stepTwo, handleErrorOne)
stepOne().then(null, handleErrorOne).then(stepTwo)

但是下面这行等同于上面的两行:

stepOne().then(stepTwo).then(null, handleErrorOne)

Angular 的 promise 库$q基于 kriskowal 的Q库(它具有更丰富的 API,但包含您可以在 中找到的所有内容$q)。Q在 GitHub 上API 文档可能很有用。Q 实现了Promises/A+ 规范,其中详细介绍then了 Promise 解析行为的工作方式和工作原理。

编辑:

还要记住,如果你想在你的错误处理程序中打破链,它需要返回一个被拒绝的Promise或抛出一个错误(这将被捕获并自动包装在一个被拒绝的Promise中)。如果您不返回Promise,then请将返回值包装在为您解决的Promise中。

这意味着,如果您不返回任何内容,您实际上是在为 value 返回一个已解决的 promise undefined

并没有真正为所提出的问题提供明确的解决方案,不过很好的解释
2021-03-14 17:10:34
stepOne().then(stepTwo, handleErrorOne) ` stepOne().then(null, handleErrorOne).then(stepTwo)` 这些真的等价吗?我认为在stepOne第二行代码被拒绝的情况下将执行stepTwo但第一行只会执行handleErrorOne并停止。或者我错过了什么?
2021-03-23 17:10:34
拒绝退出当前功能吗?例如,如果拒绝被调用,则不会调用解决方案 1st ` if (bad) { reject(status); } 解决(结果);`
2021-03-30 17:10:34
这部分是黄金:if you don't return anything, you are effectively returning a resolved promise for the value undefined.谢谢@pluma
2021-04-04 17:10:34
这确实是。我正在编辑它以赋予它应有的大胆
2021-04-07 17:10:34

聚会有点晚,但这个简单的解决方案对我有用:

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

stepOne()
.then(stepTwo, chainError)
.then(stepThreee, chainError);

这使您可以摆脱链条。

帮助了我,但仅供参考,您可以在然后将其退回以在捕获中突围,例如: .then(user => { if (user) return Promise.reject('The email address already exists.') })
2021-03-15 17:10:34
只是为了澄清,如果在 stepOne() 中发生错误,那么 chainError 都会被调用对吗?如果这可取。我有一个片段可以做到这一点,不确定我是否误解了什么 - runkit.com/embed/9q2q3rjxdar9
2021-03-21 17:10:34
这是唯一正确的答案。否则,即使第 1 步有错误,第 3 步仍会执行。
2021-03-26 17:10:34
@CraigvanTonder 你可以只在一个Promise中抛出它,它会和你的代码一样工作: .then(user => { if (user) throw 'The email address already exists.' })
2021-03-27 17:10:34

你需要的是一个重复的.then()链,有一个特殊的情况开始,一个特殊的情况结束。

诀窍是获取失败案例的步骤编号,以传递到最终的错误处理程序。

  • 开始:step(1)无条件调用
  • 重复模式:将 a.then()与以下回调链接起来:
    • 成功:调用步骤(n+1)
    • 失败:抛出先前延迟被拒绝的值或重新抛出错误。
  • 完成:链接一个.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);});

演示

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

m59,这是对“我如何编写Promise以便我可以在拒绝时调用函数而不调用错误链中的所有函数?”的问题的答案。以及问题的标题,“打破Promise链并根据链中被破坏(被拒绝)的步骤调用一个函数”
2021-03-21 17:10:34
是的,就像我说的,它提供了丰富的信息,我什至在我的帖子中包含了这个解决方案(细节较少)。这种方法旨在解决问题,以便链条可以继续。虽然它可以完成我正在寻找的东西,但它不像接受的答案中的方法那么自然。换句话说,如果你想做标题和所问问题所表达的,就采取pluma的方法。
2021-03-29 17:10:34
信息丰富的答案所以值得保留,但这不是我面临的问题。我在帖子中提到了这个解决方案,这不是我要找的。请参阅我帖子顶部的演示。
2021-04-07 17:10:34

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

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

如果我理解正确,您只想显示失败步骤的错误,对吗?

这应该就像将第一个Promise的失败案例更改为这样简单:

step(1).then(function (response) {
    step(2);
}, function (response) {
    stepError(1);
    return response;
}).then( ... )

通过$q.reject()在第一步失败的情况下返回,您拒绝了该Promise,这会导致在 2nd 中调用 errorCallback then(...)

是的,我肯定看到现在不起作用。回到我的画板!
2021-03-14 17:10:34
这到底是什么……这正是我所做的!在我的帖子中看到我确实尝试过,但是链条会重新启动并运行step(2). 现在我只是再次尝试它没有发生。我很混乱。
2021-04-03 17:10:34
嗯,好的。当我更改它时,它似乎在您发布的 jsbin 中工作,但我一定错过了一些东西。
2021-04-05 17:10:34
我确实看到你提到了这一点。不过这很奇怪。return step(2);只应在step(1)成功解析时调用包含的函数
2021-04-05 17:10:34
从头开始 - 它肯定正在发生。就像我在帖子中所说的那样,如果您不使用return $q.reject(),则链条将继续运行。在这种情况下return response搞砸了。请参阅:jsbin.com/EpaZIsIp/6/edit
2021-04-10 17:10:34