本文共 4801 字,大约阅读时间需要 16 分钟。
本篇文章或许已经介绍过Promise和Async Await的基础知识,今文将深入探讨它们如何处理异常。这是特别关注点,也是开发者在使用这些特性时容易忽视的一部分。
Promise提供了两个主要方法来处理异常:.then()和.catch()。
通过.then()的第二个参数捕获异常
.then()方法接受两个参数:第一个是成功回调函数,第二个是失败回调函数。可以利用第二个参数来捕获异常。例如:
let promise = new Promise((resolve, reject) => { reject('错误');});promise.then(() => {}, (error) => { console.log(error); // '错误'});
通过.catch()捕获异常
.catch()方法是一个更简便的方式来捕获所有异常。它内部会调用 Promise.prototype.then(undefined, onRejected)
,因此可以直接用于处理失败情况:
let promise = new Promise((resolve, reject) => { reject('错误');});promise.catch((error) => { console.log(error); // '错误'});
通过链式编写了解两者的差异
有时候项目会采用链式编写方式,可以通过实验发现,.then()
的第二个参数和.catch()
存在优先级问题。由于.then()
的第二个参数会返回一个新 Promise,后续的.catch()
可能不会捕获前面的错误。例如:
let promise = new Promise((resolve, reject) => { reject('失败');});promise.then(() => {}, (err) => {}).catch((err) => { console.log(err); // 这里不会执行});
相反,如果替换.then()
的第二个参数为一个返回值,则后面的.catch()
可能无法捕获早期错误:
let promise = new Promise((resolve, reject) => { reject('失败');});promise.then(() => {}, (err1) => { return promise1; // 注意:假设 promise1 是关于同一 Promise}).then(() => {}).catch((err2) => { console.log(err2); // ReferenceError: promise1 未定义});
由此可见,.catch()
适用于整个链式结构的错误捕获,而 .then()
的第二个参数仅捕获前一个 Promise 的结果。
错误传播的优先级规则
如果你在.then()
的第二个参数中返回一个新的 Promise 或直接调用 .catch()
,错误传播的规则会发生改变。例如:
let promise = new Promise((resolve, reject) => { reject('失败');});promise.then(() => {}, (err1) => { return new Promise((resolve, reject) => { reject('后续错误'); });}).then(() => {}, (err2) => { console.log(err2); // ReferenceError: promise1 未定义});
这里的瓶颈在于,.then()
的链式结构会导致错误被传递到后续链路,而如果我们使用.catch()
,则可以捕获整个链路上的所有错误:
let promise = new Promise((resolve, reject) => { reject('失败');});promise.then(() => {}, (err1) => {}).catch((err2) => { console.log(err2); // '失败'});
避免迷失:正确使用 catch 的方法
它被建议在多层次的 Promise 链式结构中使用.catch()
来捕获所有错误,而不是在每一个.then()
中都手动添加一个失败回调。这使得代码更加简洁且易于阅读:
let promise = new Promise((resolve, reject) => { reject('失败');});promise.then(() => {}).catch((err) => { console.log('失败'); // 这里会执行});
这样的结构会更关注程序的主要逻辑,而不是繁琐的错误处理逻辑。
Promise.value穿透(Promises 的值穿透)
值穿透是指在 Promise 链式执行中,将非函数值通过链式传递下去。例如:
Promise.resolve(1).then(2).then(() => { console.log(1);});
在这个例子中,2
会被作为参数传递给链式的下一个.then(),但不会被阻止,因为它不是一个函数。这类行为可以帮助简化代码,但可能会引发一些潜在的问题。
例如:
Promise.resolve(1).then(2).then(() => { console.log(2);});
这里,2
会被直接传递给 then
的第一个参数,因为它不是一个函数,导致将 2
作为状态处理。为了ulse)的错误传播方式。
Async 函数需要结合 await 关键字来进行异步操作和错误处理。与 Promise 不同,async 函数允许我们将多个异步操作串联起来,同时在严格的同步代码中处理。此外,async 函数内可以使用 try...catch 进行错误捕获。
在外部捕捉 async 函数的错误
async 函数返回的是一个 Promise,所有的错误都会被Promise 接收,然后可以在外部通过 catch 来捕获:
async function test() { await new Promise((resolve, reject) => { reject('错误'); });}test().catch((data) => { console.log(data); // '错误'});
在 async 函数内部捕捉错误
如果需要在 async 函数内部处理错误,可以使用 try...catch 实现:
async function test() { try { await new Promise((resolve, reject) => { reject('错误'); }); } catch (error) { console.log('错误发生在内部', error); }}test();
await 后接 catch 捕捉错误
await 错误同样可以被传递到 catch 中:
async function test() { await new Promise((resolve, reject) => { reject('错误'); }).catch((data) => { console.log('错误From await', data); }); await new Promise((resolve, reject) => { reject('错误1'); }).catch((data) => { console.log('错误从await的另一个错误1', data); }); await new Promise((resolve, reject) => { reject('错误2'); }).catch((data) => { console.log('错误从 await,错误2’, data); });}test();
优化代码:封装错误处理
有时候,在多个地方重复操作相同的错误处理逻辑,可以封装到一个函数中:
const promiseValue = (promise) => { return promise.then((data) => [null, data]).catch((err) => [err]);};async function test() { let [err, data] = await promiseValue(new Promise((resolve, reject) => { reject('错误'); })); if (err) { console.log('错误发生在这里'); } let [err1, data1] = await promiseValue(new Promise((resolve, reject) => { reject('错误1'); })); if (err1) { console.log('错误1发生在这里'); } let [err2, data2] = await promiseValue(new Promise((resolve, reject) => { reject('错误2'); })); if (err2) { console.log('错误2发生在这里'); }}test();
这种封装不仅提高了代码的可读性,还减少了重复代码。
通过对 Promise 和 Async Await 的错误处理机制的比较,可以得出以下结论:
优先级和顺序:在链式结构中,.catch()
方法返回一个新的 Promise,其优先级高于 .then()
的第二个参数。因此,后面的链式操作可能会影响前面的错误传递。
错误处理的位置:及时使用 try...catch
语句能够更好地控制异常,避免在多层面的回调中迷失。这是因为 async 函数允许你在同一代码块内处理所有的异步操作和错误。
value 穿透:在 Promise 链式结构中,非函数值会自动传递下去,这可以简化代码,但在处理错误时可能会引发一些意想不到的问题。
选择的最佳方式:在实际开发中,可以根据项目需求和具体场景灵活选择。这可能包括使用默认的 .catch()
还是在 .then()
中手动捕获错误,这取决于你需要处理的层级和结构。
综合考虑:无论选择哪种方式,代码的清晰度和可读性都是关键。因此,合理拆分代码,使用适当的错误处理逻辑,可以使整个应用更加健壮和高效。
通过深入理解和正确运用 Promise 和 Async Await 的异常捕获机制,我们可以更好地管理异步代码,确保程序的高效运行和错误的顺利处理。这个知识点对于开发高质量的现代 web 应用至关重要。
转载地址:http://qumgz.baihongyu.com/