从不用 try-catch 实现的 async/await 语法说错误处理
前不久看到 Dima Grossman 写的 How to write async await without try-catch blocks in Javascript。看到标题的时候,我感到非常好奇。我知道虽然在异步程序中可以不使用 try-catch 配合 async/await 来处理错误,但是处理方式并不能与 async/await 配合得很好,所以很想知道到底有什么办法会比 try-catch 更好用。
Dima 去除 try-catch 的方法
当然套路依旧,Dima 讲到了回调地狱,Promise 链并最终引出了 async/await。而在处理错误的时候,他并不喜欢 try-catch 的方式,所以写了一个 to(promise)
来对 Promise 进行封装,辅以解构语法,实现了同步写法但类似 Node 错误标准的代码。摘抄代码如下
// to.js
export default function to(promise) {return promise.then(data => {return [null, data];}).catch(err => [err]);
}
应用示例:
import to from "./to.js";async function asyncTask(cb) {let err, user, savedTask;[err, user] = await to(UserModel.findById(1));if (!user) return cb("No user found");[err, savedTask] = await to(TaskModel({ userId: user.id, name: "Demo Task" }));if (err) return cb("Error occurred while saving task");if (user.notificationsEnabled) {const [err] = await to(NotificationService.sendNotification(user.id, "Task Created"));if (err) return cb("Error while sending notification");}cb(null, savedTask);
}
Dima 的办法让人产生的了熟悉的感觉,Node 的回调中不是经常都这样写吗?
(err, data) => {if (err) {// deal with error} else {// deal with data}
}
所以这个方法真的很有意思。不过回过头来想一想,这段代码中每当遇到错误,都是将错误消息通过 cb()
调用推出去,同时中断后续过程。像这种中断式的错误处理,其实正适合采用 try-catch。
使用 try-catch 改写上面的代码
要用 try-catch 改写上面的代码,首先要去掉 to()
封装。这样,一旦发生错误,需要使用 Promise.prototype.catch()
进行捕捉,或者使用 try-catch 对 await promise
语句进行捕捉。捕捉到的,当然是每个业务代码里 reject
出来的 err
。
然而注意,上面的代码中并没有直接使用 err
,而是使用了自定义的错误消息。所以需要对 reject 出来的 err
进一步处理成指定的错误消息。当然这难不到谁,比如
someAsync().catch(err => Project.reject("specified message"));
然后再最外层加上 try-catch 就好。所以改写之后的代码是:
async function asyncTask(cb) {try {const user = await UserModel.findById(1).catch(err => Promise.reject("No user found"));const savedTask = await TaskModel({ userId: user.id, name: "Demo Task" }).catch(err => Promise.reject("Error occurred while saving task"));if (user.notificationsEnabled) {await NotificationService.sendNotification(user.id, "Task Created").catch(err => Promise.reject("Error while sending notification"));}cb(null, savedTask);} catch (err) {cb(err);}
}
上面这段代码,从代码量上来说,并没有比 Dima 的代码减少了多少工作量,只是去掉了大量 if (err) {}
结构。不习惯使用 try-catch 的程序员找找不到中断点,但习惯了 try-catch 的程序员都知道,业务过程中一旦发生错误(异步代码里指 reject),代码就会跳到 catch
块去处理 reject 出来的值。
但是,一般业务代码 reject 出来的信息通常都是有用的。假如上面的每个业务 reject 出来的 err 本身就是错误消息,那么,用 Dima 的模式,仍然需要写
if (err) return cb(err);
而用 try-catch 的模式,就简单多了
async function asyncTask(cb) {try {const user = await UserModel.findById(1);const savedTask = await TaskModel({ userId: user.id, name: "Demo Task" });if (user.notificationsEnabled) {await NotificationService.sendNotification(user.id, "Task Created");}cb(null, savedTask);} catch (err) {cb(err);}
}
为什么?因为在 Dima 的模式中,if (err)
实际上处理了两个业务:一是捕捉会引起中断的 err
,并将其转换为错误消息,二是通过 return
中断业务过程。所以当 err
转换为错误消息这一过程不再需要的时候,这种捕捉中断再重新引起中断的处理主显得多余了。
继续改进
用函数表达式改善 try-catch 逻辑
当然还有改进的空间,比如 try {}
块中的代码比较长,会造成阅读不太方便,try-catch 的逻辑有被“切断”的感觉。这种情况下可以使用函数表达式来改善
async function asyncTask(cb) {async function process() {const user = await UserModel.findById(1);const savedTask = await TaskModel({ userId: user.id, name: "Demo Task" });if (user.notificationsEnabled) {await NotificationService.sendNotification(user.id, "Task Created");}return savedTask;}try {cb(null, await process());} catch (err) {cb(err);}
}
如果对错误的处理代码比较长,也可以写成单独的函数表达式。
如果过程中每一步的错误处理逻辑不同怎么办
如果发生错误,不再转换为错误消息,而是特定的错误处理逻辑,怎么办?
思考一下,我们用字符串来表示错误消息,以后可以通过 console.log()
来处理处理。而逻辑,最适合的表示当然是函数表达式,最终可以通过调用来进行统一处理
async function asyncTask(cb) {async function process() {const user = await UserModel.findById(1).catch(err => Promise.reject(() => {// deal with error on looking for the userreturn "No user found";}));const savedTask = await TaskModel({ userId: user.id, name: "Demo Task" }).catch(err => Promise.reject(() => {// making model error// deal with itreturn err === 1? "Error occurred while saving task": "Error occurred while making model";}));if (user.notificationsEnabled) {await NotificationService.sendNotification(user.id, "Task Created").catch(err => Promise.reject(() => {// just print a messagelogger.log(err);return "Error while sending notification";}));}return savedTask;}try {cb(null, await process());} catch (func) {cb(func());}
}
甚至还可以处理更复杂的情况
现在应该都知道 .catch(err => Promise.reject(xx))
,这里的 xx
就是 try-catch 的 catch 块捕捉到的对象,所以如果不同的业务 reject 出来不同的对象,比如有些是函数(表示错误处理逻辑),有些是字符串(表示错误消息),有些是数字(表示错误代码)——其实只需要改 catch 块就行
try {// ... } catch(something) {switch (typeof something) {case "string":// show message somethingbreak;case "function":something();break;case "number":// look up something as code// and show correlative messagebreak;default:// deal with unknown error}}
小结
我没有批判 Dima 的错误处理方式,这个错误处理方式很好,很符合 Node 错误处理的风格,也一定会受到很多人的喜爱。由于 Dima 的错误处理方式给带灵感,同时也让我再次审视了一直比较喜欢的 try-catch 方式。
用什么方式取决于适用场景、团队约定和个人喜好等多种因素,在不同的情况下需要采用不同的处理方式,并不是说哪一种就一定好于另一种——合适的才是最好的!
本文转自边城__ 51CTO博客,原文链接:http://blog.51cto.com/jamesfancy/2062422,如需转载请自行联系原作者
从不用 try-catch 实现的 async/await 语法说错误处理相关推荐
- Async/Await语法糖
Async/Await语法糖 语言层面的异步编程标准 有了 Generator 之后 js 中的异步编程基本上就已经与同步代码有类似的体验了,但是使用 Generator 这种方案我们还需要去手动编辑 ...
- Javascript社区是时候接受async/await语法了
由于Javascript是一个单线程语言,大量的API都是异步实现的.异步代码有一个很讨厌的问题,会传染.当你在一个函数中使用一个异步API时,你需要通过回调执行后续的逻辑,而当外层逻辑使用这个函数并 ...
- 前端异步请求async/await,axios的错误用法
request为包装后的axios请求 1.直接return请求,得到promise,获取不到返回值, const tmp = async () => {return request({meth ...
- async function_掌握 Async/Await
摘要: 还不用Async/Await就OUT了.. 原文:掌握 Async/Await 作者:Jartto Fundebug经授权转载,版权归原作者所有. 前端工程师肯定都经历过 JS 回调链狱的痛苦 ...
- 在微信小程序中使用 async/await
微信小程序中有大量接口是异步调用,比如 wx.login() . wx.request() . wx.getUserInfo() 等,都是使用一个对象作为参数,并定义了 success() . fai ...
- promise 和 async await区别
什么是Async/Await? async/await是写异步代码的新方式,以前的方法有回调函数和Promise. async/await是基于Promise实现的,它不能用于普通的回调函数. as ...
- Async/Await替代Promise的6个理由
2019独角兽企业重金招聘Python工程师标准>>> 译者按: Node.js的异步编程方式有效提高了应用性能:然而回调地狱却让人望而生畏,Promise让我们告别回调函数,写出更 ...
- 解决异步问题,教你如何写出优雅的promise和async/await,告别callback回调地狱!
解决异步问题--promise.async/await 一.单线程和异步 1.单线程是什么 2.为什么需要异步 3.使用异步的场景 二.promise 1.promise的三种状态 2.三种状态的表现 ...
- Python 异步 IO 、协程、asyncio、async/await、aiohttp
From :廖雪峰 异步IO :https://www.liaoxuefeng.com/wiki/1016959663602400/1017959540289152 Python Async/Awai ...
最新文章
- linux vino vnc,vino-server和vncserver在教学环境中的典型应用
- CMDB 设计(二)实现host、ip存储
- 将tomcat添加到服务中
- 转:GridView 模板列中的数据绑定
- 【Python】如何获取Numpy三维数组中目标值的位置
- 游侠怎么设置java路径_Java获取当前路径的代码
- IT. IT-hyena成就自我
- Mac os安装SEAL
- python学习笔记之——装饰器及对象
- 关于科研和工作的几点思考
- C#Winform使用火狐firefox内核GeckoWebBrowser
- Simulink模块介绍
- postgresql 中文azw3_制作mobi格式的PostgreSQL文档
- mysql mtq_Mysql 入门学习指南
- 用Cython加密Python代码这么简单
- 有高度的数据分析,这样做
- css特效实例——纯css实现带边角卷边阴影的纸
- 2021.07.28
- Bloom filter 过滤(布隆过滤算法)原理
- Linux Cobbler批量装机
热门文章
- 互联网思维-标签思维(1)
- LeetCode 8 字符串转整数 (atoi)
- Flutter开发之路由跳转与传参(七)
- 如何使用cmd进入打印机选项_怎样用命令行方式添加打印机端口? (已解决)
- python基础笔记(非系统/自用/参考小甲鱼的零基础入门学习python)下
- 【洛谷P3846】【TJOI2007】—可爱的质数(BSGS模板)
- 剑指offer python版 找出数组中重复的数字
- 在Windows Server 2012 R2中搭建SQL Server 2012故障转移集群
- spring 找不到applicationContext.xml解决方法
- 初创公司谁来当你们的运维