拒绝后承诺链继续

Posted

技术标签:

【中文标题】拒绝后承诺链继续【英文标题】:Promise chain continues after rejection 【发布时间】:2021-05-31 09:00:50 【问题描述】:

我无法正确捕获承诺链中的错误/拒绝。

const p1 = () => 
    return new Promise((resolve, reject) => 
        console.log("P1");
        resolve();
    );
;

const p2 = () => 
    return new Promise((resolve, reject) => 
        console.log("P2");
        reject();
    );
;


const p3 = () => 
    return new Promise((resolve, reject) => 
        console.log("P3");
        resolve();
    );
;

p1().catch(() => 
    console.log("Caught p1");
).then(p2).catch(() => 
    console.log("Caught p2");
).then(p3).catch(() => 
    console.log("Caught p3");
).then(() => 
    console.log("Final then");
);

当 promise 被拒绝时,下面的 .then 仍然会被执行。据我了解,当在 Promise 链中发生错误/拒绝时,随后的 .then 调用将不再执行。

P1
P2
Caught p2
P3
Final then

拒绝被正确捕获,但为什么在捕获后记录“P3”?

我做错了什么?

为了澄清@evolutionxbox,这是我的预期结果:

Promise.resolve().then(() => 
    console.log("resolve #1");
    return Promise.reject();
).then(() => 
    console.log("resolve #2");
    return Promise.resolve();
).then(() => 
    console.log("resolve #3");
    return Promise.resolve();
).then(() => 
    console.log("Final end");
).catch(() => 
    console.log("Caught");
);

此代码的工作方式与应有的完全一样。而且我看不出我的代码有什么不同,除了我分别声明了函数。

无论promise在哪里被拒绝,上面的代码都会停止。

【问题讨论】:

拒绝后的第一个 .catch() 负责处理错误,然后继续默认链。 @Sirko 这正是发生的事情。但是为什么?我认为链条在第一次拒绝时“中止”。如何在拒绝时停止执行以下功能?为什么我不能对所有承诺使用“全局”捕获? 您在从 p2 捕获错误后调用 p3。 catch 处理错误,然后像没有发生一样继续进行 catch 的返回值是一个承诺,所以它会有一个 then 方法对吗? developer.mozilla.org/en-US/docs/Web/javascript/Reference/…为什么你认为它应该停止? 您的假设不正确,仅此而已。 .catch 不会停止承诺链,原因与常规 try .. catch 不会停止位于 catch 之后的代码的代码执行相同。如果这样做就没有意义 【参考方案1】:

这是您的代码的同步等效项:

const f1 = () => 
  console.log("F1");
;

const f2 = () => 
  console.log("F2");
  throw new Error();
;

const f3 = () => 
  console.log("F3");
;

try 
  f1();
 catch 
  console.log("Caught f1");


try 
  f2();
 catch 
  console.log("Caught f2");


try 
  f3();
 catch 
  console.log("Caught f3");


console.log("Final code");

如您所见,这给出了匹配的结果。希望看到同步代码你不会对为什么感到惊讶。在try..catch 中,您可以尝试恢复。这个想法是catch停止错误传播,您可以希望继续进一步。或者如果您确实想停止,您仍然需要再次明确地throw,例如:

doCode();

try 
    makeCoffee();
 catch(err) 
    if (err instanceof IAmATeapotError) 
        //attempt recovery
        makeTea();
     else 
        //unrecoverable - log and re-throw
        console.error("Fatal coffee related issue encountered", err);
        throw err;
    


doCode();

这也是Promise#catch() 服务的目的——因此您可以尝试恢复或至少在出现问题时采取行动。这个想法是,.catch() 之后,您也许可以继续:

const orderPizza = (topping) => 
  new Promise((resolve, reject) => 
    if (topping === "pepperoni")
      reject(new Error("No pepperoni available"));
    else
      resolve(`$topping pizza`);
  );

const makeToast = () => "toast";
const eat = food => console.log(`eating some $food`);

async function main() 
  await orderPizza("cheese")
    .catch(makeToast)
    .then(eat);
    
  console.log("-----");
  
  await orderPizza("pepperoni")
    .catch(makeToast)
    .then(eat);


main();

为了拒绝来自.catch() 的承诺链,您需要在错误恢复时通过引发另一个错误来执行与普通catchfail 类似的操作。 You can throw or return a rejected promise to that effect.

此代码的工作方式与应有的完全一样。而且我看不出我的代码有什么不同,除了我分别声明了函数。

无论promise在哪里被拒绝,上面的代码都会停止。

您显示的第二段代码在拒绝后完全失败,因为没有其他成功的.catch()-es。和这个同步代码基本类似:

try 
    console.log("log #1");
    throw new Error();
    console.log("log #2");
    console.log("log #3");
    console.log("Final end");
 catch 
    console.log("Caught");

因此,如果您不想提前恢复,也可以跳过.catch(),而不是引发另一个错误。

【讨论】:

【参考方案2】:

试试这个。

const p1 = (arg) => 
  // Promise returns data in the respected arguments
  return new Promise((resolve, reject) => 
    // Data to be accessed through first argument.
    resolve(arg);

  );
;


const p2 = (arg) => 
  return new Promise((resolve, reject) => 

    // Data to be accessed through second argument.
    reject(arg);

  );


p1('p1').then(resolve => 
  console.log(resolve + ' is handled with the resolve argument. So it is accessed with .then()');
) // Since reject isn't configured to pass any data we don't use .catch()

p2('p2').catch(reject => 
  console.log(reject + ' is handled with the reject argument. So it is accessed with .catch()');
) // Since resolve ins't configured to pass any data we don't use .then()

// You would normally configure a Promise to return a value on with resolve, and access it with .then() when it completes a task successfully.

// .catch() would then be chained on to the end of .then() to handle errors when a task cannot be completed.

// Here is an example.

const p3 = () => 
  return new Promise((resolve, reject) => 

    var condition = true;

    if (condition === true) 
      resolve('P3');
     else 
      reject('Promise failed!');
    

  );
;

p3('p3').then(resolve => 
  console.log(resolve);
).catch(reject => 
  console.log(reject);
)

【讨论】:

【参考方案3】:

你没有做错任何事。 在您的代码中,您调用第一个承诺 p1。然后你写p1.catch(...).then(...).then(...).then(...)。这是一个链,意味着您应该调用 then 3 次,因为您在 p1 承诺中调用了 resolve 方法(所有这些 thens 都依赖于第一个承诺)。

【讨论】:

这不是我的问题。如果我拒绝承诺,为什么链条不会“中止”。其余的仍在执行。我更新了我的问题,并提供了我的代码应该如何工作的工作示例。【参考方案4】:

当 promise 被拒绝时,下面的 .then 仍然会被执行。

是的。准确地说:thencatch 方法调用都是同步执行的(一口气),因此所有涉及的 Promise 都是一次性创建的。传递给这些方法的 回调 是异步执行的,因为相关的 Promise 解决(完全填充或拒绝)。

据我了解,当在承诺链中发生错误/拒绝时,其后的 .then 调用将不再执行。

事实并非如此。 catch 返回的 Promise 可以根据传递给它的回调中发生的情况来填充或拒绝 ,因此当该 Promise 解决时,更下游的回调将相应地执行。

拒绝被正确捕获,但为什么在捕获后记录“P3”?

在您的情况下,catch 回调返回undefined(它只执行console.log),它的承诺fulls!结果,链式的then 回调——在 that promise 上——被执行......等等。

如果你想“停止”

如果你想保持链原样,但希望有一种行为,即拒绝导致不再执行 thencatch 回调,那么不要解决相关的承诺:

const stop = new Promise(resolve => null);

const p1 = () => 
    return new Promise((resolve, reject) => 
        console.log("P1");
        resolve();
    );
;

const p2 = () => 
    return new Promise((resolve, reject) => 
        console.log("P2");
        reject();
    );
;


const p3 = () => 
    return new Promise((resolve, reject) => 
        console.log("P3");
        resolve();
    );
;

p1().catch(() => 
    console.log("Caught p1");
    return stop; // don't resolve
).then(p2).catch(() => 
    console.log("Caught p2");
    return stop;
).then(p3).catch(() => 
    console.log("Caught p3");
    return stop;
).then(() => 
    console.log("Final then");
);

【讨论】:

new Promise(resolve => null) 将停止整个承诺链。我不确定这是一个很好的主意,因为它使以后的任何恢复都变得不可能。即使是附加.finally() 之类的东西也不会被处理。如果不应该进行恢复尝试,我建议要么不附加.catch(),因为知道承诺链失败通常比让它永远陷入困境更有价值。另一种方法是在.catch() 中通过抛出错误或返回被拒绝的承诺来拒绝。这仍然使链条的其余部分失败。 实际上它会停止整个承诺链。那是我对OP愿望的理解。我自己永远不会这样做,但我也不希望事情就这样停止。

以上是关于拒绝后承诺链继续的主要内容,如果未能解决你的问题,请参考以下文章

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

中途停止承诺链

打破承诺链 - 停止执行下一个“then”

如何从内部拒绝承诺然后起作用

链式承诺不会在拒绝时传递

异常被承诺链吞噬