【问题标题】: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 之后的代码的代码执行相同。如果这样做就没有意义

标签: javascript promise chain waterfall unhandled-promise-rejection


【解决方案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愿望的理解。我自己永远不会这样做,但我也不希望事情就这样停止。
      猜你喜欢
      • 1970-01-01
      • 2019-11-25
      • 2013-09-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多