async、await 与 promise 的错误处理
一、结论
先说结论,对于通过 new Promise 等创建的 promise 实例,一定要用 reject 处理报错信息,可以通过这里写入详细的出错信息以及出错的来源,以便调用方能够跟踪错误的信息。(即下面测试案例中的方案二)。而且 reject 的处理,调用方是会正常收到错误提示的,也会阻断后续的脚本操作。
而对于 async 和 await 创建的 promise 因为没有 reject 处理的入口,所以比较适合两种处理方案:1、不主动捕捉错误,让出错的信息直接抛到调用方。2、有需要特殊处理错误的,则在 catch 里处理完,对有需要往上抛错误的场景则需要再通过 throw 的方式向调用方传递错误信息。
另外,由于 async 和 await 无法直接处理 resolve 与 reject ,因此,如果是编写一个公用的方法,不建议直接用 async 去编写,最好是自定义一个 promise ,然后将 async 包裹在这里面,这样才能够处理 resolve 与 reject。比如下方示例:
// 用 setTimeout 模拟一个异步操作
function timeoutPromise(content, time, func) {
return new Promise((resolve, reject) => {
try {
func(); // 用来触发错误
setTimeout(() => {
console.log(content, time);
resolve && resolve(content);
}, time);
console.log(content, '再做点啥');
} catch (error) {
reject({
source: `timeoutPromise ${content}`,
error: error
})
}
});
}
// 公用方法建议采用这种
function asyncFunc() {
return new Promise(async (resolve, reject) => {
await timeoutPromise('异步1转同步', 50, () => {});
return await Promise.all([
timeoutPromise('异步2', 100, () => {}),
timeoutPromise('异步3 会出错的这一步', 200),
timeoutPromise('异步4', 300, () => {}),
]).then((result) => {
resolve(result);
}).catch((error) => {
reject({
source: `${error.source} => asyncFunc`,
error: error.error
});
});
});
}
// 这种方式只适合作为最后执行的流程,或非公用的方法
async function asyncFunc() {
await timeoutPromise('异步1转同步', 50, () => {});
return await Promise.all([
await timeoutPromise('异步2', 100, () => {}),
await timeoutPromise('异步3 会出错的这一步', 200),
await timeoutPromise('异步4', 300, () => {}),
]);
}
二、测试案例
1、html
<button id="btn">测试 async 函数的异常处理</button>
<script src="./async.js"></script>
<script>
document.querySelector('#btn').addEventListener('click', start);
</script>
2、js
/**
* 方案一:每一层都不捕捉错误
* 最底层会直接往上抛出错误
* 中间的 promise.all 除了出错的异步不能正常执行外,其他的异步都会全部执行完毕。同时也会往上抛出错误
* 最上层的 start 也会从出错的异步那里停止执行,同时直接向浏览器抛出错误
* 这种方式虽然最上层能够捕捉到错误,但由于不知是哪一层出的错,所以不好跟踪错误
*/
// // 用 setTimeout 模拟一个异步操作
// function timeoutPromise(content, time, func) {
// return new Promise((resolve, reject) => {
// func(); // 用来触发错误
// setTimeout(() => {
// console.log(content, time);
// resolve && resolve(content);
// }, time);
// console.log(content, '再做点啥');
// });
// }
// async function asyncFunc() {
// await timeoutPromise('异步1转同步', 50, () => {});
// return Promise.all([
// timeoutPromise('异步2', 100, () => {}),
// timeoutPromise('异步3 会出错的这一步', 200),
// timeoutPromise('异步4', 300, () => {}),
// ]);
// }
// async function start() {
// console.log('start');
// await asyncFunc();
// console.log('end');
// }
/** 方案二:最底层采用 reject 处理错误
* 与方案一一样每一层都会抛出错误,但不同的地方是这种方式可以在最底层的错误信息里加上来源,从而提高错误处理的效率
*/
// 用 setTimeout 模拟一个异步操作
// function timeoutPromise(content, time, func) {
// return new Promise((resolve, reject) => {
// try {
// func(); // 用来触发错误
// setTimeout(() => {
// console.log(content, time);
// resolve && resolve(content);
// }, time);
// console.log(content, '再做点啥');
// } catch (error) {
// reject({
// source: `timeoutPromise ${content}`,
// error: error
// })
// }
// });
// }
// async function asyncFunc() {
// await timeoutPromise('异步1转同步', 50, () => {});
// return Promise.all([
// timeoutPromise('异步2', 100, () => {}),
// timeoutPromise('异步3 会出错的这一步', 200),
// timeoutPromise('异步4', 300, () => {}),
// ]);
// }
// async function start() {
// console.log('start');
// await asyncFunc();
// console.log('end');
// }
/** 方案三:最底层采用 reject 处理错误,中间层采用了 try catch 的方式
* 这种方案的特点是:timeoutPromise 会抛出错误给 asyncFunc,但 asyncFunc 不会抛出错误给 start。
* 因此 start 会将后面的脚本都执行完。
* 如果希望出错的时候不要出现
*/
// // 用 setTimeout 模拟一个异步操作
// function timeoutPromise(content, time, func) {
// return new Promise((resolve, reject) => {
// try {
// func(); // 用来触发错误
// setTimeout(() => {
// console.log(content, time);
// resolve && resolve(content);
// }, time);
// console.log(content, '再做点啥');
// } catch (error) {
// reject({
// source: `timeoutPromise ${content}`,
// error: error
// })
// }
// });
// }
// async function asyncFunc() {
// try {
// await timeoutPromise('异步1转同步', 50, () => {});
// Promise.all([
// timeoutPromise('异步2', 100, () => {}),
// timeoutPromise('异步3 会出错的这一步', 200),
// timeoutPromise('异步4', 300, () => {}),
// ]);
// } catch (error) {
// console.log('asyncFunc 捕捉并处理了错误', error);
// }
// }
// async function start() {
// console.log('方案三start');
// await asyncFunc();
// console.log('end');
// }
/** 方案四:timeoutPromise 和 asyncFunc 都用 reject 处理错误
* 与方案三一样,asyncFunc 不会抛出错误,导致 start 无法及时捕捉到错误
*/
// // 用 setTimeout 模拟一个异步操作
// function timeoutPromise(content, time, func) {
// return new Promise((resolve, reject) => {
// try {
// func(); // 用来触发错误
// setTimeout(() => {
// console.log(content, time);
// resolve && resolve(content);
// }, time);
// console.log(content, '再做点啥');
// } catch (error) {
// reject({
// source: `timeoutPromise ${content}`,
// error: error
// })
// }
// });
// }
// async function asyncFunc() {
// await timeoutPromise('异步1转同步', 50, () => {});
// return Promise.all([
// timeoutPromise('异步2', 100, () => {}),
// timeoutPromise('异步3 会出错的这一步', 200),
// timeoutPromise('异步4', 300, () => {}),
// ]).catch((error) => {
// console.log(error);
// });
// }
// async function start() {
// console.log('方案四start');
// await asyncFunc();
// console.log('end');
// }
/** 方案五:timeoutPromise 用 reject 处理错误,asyncFunc 用 catch 重新抛出错误
* 这个方案其实跟方案二类似。如果在 asyncFunc 有需要对底层的错误进行处理的,则可以加这一层,否则用方案二即可。
*/
// 用 setTimeout 模拟一个异步操作
function timeoutPromise(content, time, func) {
return new Promise((resolve, reject) => {
try {
func(); // 用来触发错误
setTimeout(() => {
console.log(content, time);
resolve && resolve(content);
}, time);
console.log(content, '再做点啥');
} catch (error) {
reject({
source: `timeoutPromise ${content}`,
error: error
})
}
});
}
async function asyncFunc() {
await timeoutPromise('异步1转同步', 50, () => {});
return Promise.all([
timeoutPromise('异步2', 100, () => {}),
timeoutPromise('异步3 会出错的这一步', 200),
timeoutPromise('异步4', 300, () => {}),
]).catch((error) => {
throw error;
});
}
async function start() {
console.log('方案五start');
await asyncFunc();
console.log('end');
}
555
1
555
555
1
1
1
1
1
1
1
1
1
1
1
1
555
1
1
1