传统的错误处理方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function thisThrows ( ) { throw new Error ("Thrown from thisThrows()" ); } try { thisThrows (); } catch (e) { console .error (e); } finally { console .log ('We do cleanup here' ); }
非常简单,不过多赘述
一个包装在Async中的Try Catch 现在我们修改这个程序,把thisThrows()函数标记为async。此时抛出的错误,实际上相当于抛出一个Reject。一个Async函数,总是返回一个Promise
当没有定义返回语句的时候,函数运行结束后实际返回的是Promise,相当于return Promise.Resolve()
当有定义返回语句的时候,相当于返回了一个带有值的Promise,相当于return Promise.Resolve('My return String')
当抛出错误的时候,相当于return Promise.Reject(error)
看下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 async function thisThrows ( ) { throw new Error ("Thrown from thisThrows()" ); } try { thisThrows (); } catch (e) { console .error (e); } finally { console .log ('We do cleanup here' ); }
thisThrows
返回一个Reject,所以我们使用常规的try...catch
无法正常的捕捉到错误。
thisThrws
标记为async,所以我们调用的时候,代码不会等待,finally块会先执行,所以这里无法捕捉到错误。
有两个方式可以解决这个问题:
第一个解决方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 async function thisThrows ( ) { throw new Error ("Thrown from thisThrows()" ); } async function run ( ) { try { await thisThrows (); } catch (e) { console .error (e); } finally { console .log ('We do cleanup here' ); } } run ();
第二个解决方式 1 2 3 4 5 6 7 8 9 10 11 12 async function thisThrows ( ) { throw new Error ("Thrown from thisThrows()" ); } thisThrows () .catch (console .error ) .then (() => console .log ('We do cleanup here' ));
async/await的方式相对来说更容易理解。
注意点 从async函数中返回 考虑下下面代码会输出什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 async function thisThrows ( ) { throw new Error ("Thrown from thisThrows()" ); } async function myFunctionThatCatches ( ) { try { return thisThrows (); } catch (e) { console .error (e); } finally { console .log ('We do cleanup here' ); } return "Nothing found" ; } async function run ( ) { const myValue = await myFunctionThatCatches (); console .log (myValue); } run ();
我们可能期待输出
1 2 We do cleanup here Nothing Found
实际输出一个UnhandledPromiseRejection
我们分析下
thisThrows() 是异步方法;
异步方法中抛出了一个错误,实际返回的是Promise.Reject
myFunctionThatCatches中返回了这个Promise.Reject
外部是以await标记的,发现是一个Reject的Prmoise,所以抛出unlandled promise rejection
我们可以在返回中增加await解决这个问题(第七行)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 async function thisThrows ( ) { throw new Error ("Thrown from thisThrows()" ); } async function myFunctionThatCatches ( ) { try { return await thisThrows (); } catch (e) { console .error (e); } finally { console .log ('We do cleanup here' ); } return "Nothing found" ; } async function run ( ) { const myValue = await myFunctionThatCatches (); console .log (myValue); } run ();
重置stack trace 在代码中,经常会看到有人捕获错误并将其包装在一个新的错误中,就像下面的代码片段中一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 function thisThrows ( ) { throw new Error ("Thrown from thisThrows()" ); } function myFunctionThatCatches ( ) { try { return thisThrows (); } catch (e) { throw new TypeError (e.message ); } finally { console .log ('We do cleanup here' ); } } async function run ( ) { try { await myFunctionThatCatches (); } catch (e) { console .error (e); } } run ();
注意我们的堆栈跟踪仅从我们捕获原始异常的地方开始。当我们在 2
行创建错误并在 9
行捕获它时,我们会丢失原始的堆栈跟踪,因为我们现在创建了一个新的 TypeError
类型的错误,只保留原始的错误消息(有时我们甚至都不保留)。
如果 thisThrows()
函数中有更多的逻辑,在该函数的某个地方抛出了一个错误,我们在记录的堆栈跟踪中看不到问题的起源,因为我们创建了一个新的错误,它将生成一个全新的堆栈跟踪。如果我们只是重新抛出原始错误,我们就不会遇到这个问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 function thisThrows ( ) { throw new Error ("Thrown from thisThrows()" ); } function myFunctionThatCatches ( ) { try { return thisThrows (); } catch (e) { throw e; } finally { console .log ('We do cleanup here' ); } } async function run ( ) { try { await myFunctionThatCatches (); } catch (e) { console .error (e); } } run ();
堆栈跟踪现在指向实际错误的起源,即我们脚本的第 2
行。
处理错误时要意识到这个问题是很重要的。有时这可能是可取的,但通常这会掩盖问题的来源,使得调试问题的根源变得困难。如果你为包装错误创建自定义错误,请确保跟踪原始的堆栈跟踪,以免调试变成一场噩梦。
总结
我们可以使用 try...catch
来处理同步代码。
我们可以使用 try...catch
(与 async
函数结合使用)和 .catch()
方法来处理异步代码的错误。
在 try
块中返回一个promise时,如果你希望 try...catch
块捕获错误,请确保 await
它。
在包装错误并重新抛出时,请注意,您会丢失带有错误来源的堆栈跟踪。