【js】如何较为“优雅”得写 async 及 await 异常处理
Jun 10, 2023笔记jses【js】如何较为“优雅”得写 async 及 await 异常处理
async
/await
不必多说、它是 ES7 中引入的异步编程模型,它使异步代码看起来像同步代码,更易于阅读和编写。async
函数返回一个 Promise 对象,可以使用await
关键字等待 Promise 对象的解决。
如:
1 | async function getData() { |
当使用 async await
处理异步函数的时候,通常我们会用 try-catch
来做容错或捕获异常,很多代码、文档文章都如此建议,比如 mdn 中重写 promise 链的说明:
原本 promise:
1 | function getProcessedData(url) { |
改为async await
:
1 | async function getProcessedData(url) { |
因此我们可以在各种 async
异步函数定义的代码中看到大量的 try-catch
。这些 try-catch
能帮我们兜底各类异常,但是大量的 try-catch
始终让人感觉可以做一些抽象处理(代码结构的改变、看起来总觉得有冗余代码),而且很多同学在开发时甚至可能会忘记 try-catch
之类的兜底处理。那么我们该如何“优雅”得进行async
及 await
异步处理呢?
异常处理的时机
首先我们需要分析一下平时需要加try-catch
的场景:
1.处理异步调用时 Promise 执行的异常
在进行异步调用时,在执行 Promise 期间可能会发生某些异常(比较多的是接口请求相关,比如接口请求连接错误、超时等),一旦出现上述情况,异步请求就会产生异常,而我们知道 js 是单线程语言。代码报错后,后面的代码无法继续执行,所以需要加一个 try-catch
来捕获此时的异步请求,让代码可以继续向后执行。
比如:
1 | async function getUserId() { |
当然多个异步调用时也同样会考虑使用try-catch
进行处理:
1 | async function getUserAsset() { |
2.处理异步调用返回值的异常
在进行异步调用时,由于返回值也存在不确定性,因此 try-catch
也常用于 await
返回值的处理、通常是取值赋值。如:
1 | async function getUserList() { |
对应于这两种不同的情况,“优雅”得处理方式也可以有所差异
不用try-catch
进行异常处理
1.面向处理异步调用时 Promise 执行的异常
这类情况触发异常的主要原因通常是因为 Promise 没有进行catch
处理、从而使异常暴露到了调用侧,因此最简单粗暴的方式就是在 Promise 调用上加catch
,如:
1 | async function getUserId() { |
当然假如我们不希望异常后继续处理(比如不是上述取值函数、而是个 step-by-step 的执行),我们可以在catch
中通过reject
阻止继续执行,如:
1 | async function run() { |
或者如果step1()
、step2()
如果有正常返回值设计的话也可以通过判断返回值进行中断。
但是综合来看,手动加 catch的方式仍然有一定成本,开发同学同样会忘记,并且 error 的处理还是很困难,总的来说还是不够“优雅”。
2.面向处理异步调用返回值的异常
这类情况就需要通过各类数据判断进行约束,比如上述接口取值的情况:
1 | async function getUserList() { |
不过有些时候这么处理会使得代码很臃肿。
await-to-js
处理函数
有一个小范围有名的await
异常处理的封装模块:await-to-js,Github 源码>>。它就较好得解决了await
异步处理异常的问题。
使用
安装
1 | npm i await-to-js --save |
然后在项目中import
对应方法即可:
1 | import to from 'await-to-js' |
其中参数
promise
:{Promise},需要包裹处理的 promise 执行 errorExt
:{object},异常信息的补充对象, 可选
返回值:[U, undefined]
或 [null, T]
- 异常时返回前者,
U
为 catch 返回值 - 正常时返回后者,
T
为 Promise 返回数据
然后我们可以根据err
的存在与否来判断状态并执行后续处理。我们可以使用await-to-js
来改造刚才这些方法
改造
1 | import to from 'await-to-js'; |
1 | import to from 'await-to-js'; |
1 | import to from 'await-to-js'; |
原理分析
await-to-js
源码:
1 | /** |
源码很简单只有 22 行,大致流程如下: 函数接受参数 promise
和 errorExt
。
如果 Promise 成功,它会返回 [null, data]
。
如果异常,则判断是否有 errorExt
参数(代表传递给 err
对象的附加信息)。如果它有时与 catch
返回捕获的 err
合并,或者 [err, undefined]
如果没有。
设计分析
await-to-js
的返回格式是否让你感到熟悉?本人的第一印象就是很想 Nodejs 异常优先的回调设计。在 Nodejs 的 API 中,回调函数的第一个参数是 error
,是为了方便处理错误。如果异步操作没有出错,error
参数为 null
或 undefined
,否则它会包含一个 Error
对象,其中包含有关错误的信息。比如:
1 | const fs = require('fs'); |
这种模式的优点是可以在回调函数中优先处理错误,而不是在调用异步函数之后检查错误。这样可以避免在异步操作之间混淆错误检查和处理代码。此外,这种模式还可以使您的代码更加简洁和易于阅读。
当前其他编程语言中也有相似的设计,比如await-to-js
作者在博客中提到的 go-lang
1 | data, err := db.Query("SELECT ...") |
这种设计感觉比使用 try-catch
块更干净,并且更少地聚集代码,这使得代码更加可读和可维护。
相关链接
- 《How to write async await without try-catch blocks in Javascript》https://blog.grossman.io/how-to-write-async-await-without-try-catch-blocks-in-javascript/
- 《Stop Using try-catch to Catch Async/Await Exceptions》https://javascript.plainenglish.io/stop-using-try-catch-to-catch-async-await-exceptions-6e0215ace654
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_function
Author
My name is Micheal Wayne and this is my blog.
I am a front-end software engineer.
Contact: michealwayne@163.com