前不久看到 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 语法说错误处理相关推荐

  1. Async/Await语法糖

    Async/Await语法糖 语言层面的异步编程标准 有了 Generator 之后 js 中的异步编程基本上就已经与同步代码有类似的体验了,但是使用 Generator 这种方案我们还需要去手动编辑 ...

  2. Javascript社区是时候接受async/await语法了

    由于Javascript是一个单线程语言,大量的API都是异步实现的.异步代码有一个很讨厌的问题,会传染.当你在一个函数中使用一个异步API时,你需要通过回调执行后续的逻辑,而当外层逻辑使用这个函数并 ...

  3. 前端异步请求async/await,axios的错误用法

    request为包装后的axios请求 1.直接return请求,得到promise,获取不到返回值, const tmp = async () => {return request({meth ...

  4. async function_掌握 Async/Await

    摘要: 还不用Async/Await就OUT了.. 原文:掌握 Async/Await 作者:Jartto Fundebug经授权转载,版权归原作者所有. 前端工程师肯定都经历过 JS 回调链狱的痛苦 ...

  5. 在微信小程序中使用 async/await

    微信小程序中有大量接口是异步调用,比如 wx.login() . wx.request() . wx.getUserInfo() 等,都是使用一个对象作为参数,并定义了 success() . fai ...

  6. promise 和 async await区别

     什么是Async/Await? async/await是写异步代码的新方式,以前的方法有回调函数和Promise. async/await是基于Promise实现的,它不能用于普通的回调函数. as ...

  7. Async/Await替代Promise的6个理由

    2019独角兽企业重金招聘Python工程师标准>>> 译者按: Node.js的异步编程方式有效提高了应用性能:然而回调地狱却让人望而生畏,Promise让我们告别回调函数,写出更 ...

  8. 解决异步问题,教你如何写出优雅的promise和async/await,告别callback回调地狱!

    解决异步问题--promise.async/await 一.单线程和异步 1.单线程是什么 2.为什么需要异步 3.使用异步的场景 二.promise 1.promise的三种状态 2.三种状态的表现 ...

  9. Python 异步 IO 、协程、asyncio、async/await、aiohttp

    From :廖雪峰 异步IO :https://www.liaoxuefeng.com/wiki/1016959663602400/1017959540289152 Python Async/Awai ...

最新文章

  1. linux vino vnc,vino-server和vncserver在教学环境中的典型应用
  2. CMDB 设计(二)实现host、ip存储
  3. 将tomcat添加到服务中
  4. 转:GridView 模板列中的数据绑定
  5. 【Python】如何获取Numpy三维数组中目标值的位置
  6. 游侠怎么设置java路径_Java获取当前路径的代码
  7. IT. IT-hyena成就自我
  8. Mac os安装SEAL
  9. python学习笔记之——装饰器及对象
  10. 关于科研和工作的几点思考
  11. C#Winform使用火狐firefox内核GeckoWebBrowser
  12. Simulink模块介绍
  13. postgresql 中文azw3_制作mobi格式的PostgreSQL文档
  14. mysql mtq_Mysql 入门学习指南
  15. 用Cython加密Python代码这么简单
  16. 有高度的数据分析,这样做
  17. css特效实例——纯css实现带边角卷边阴影的纸
  18. 2021.07.28
  19. Bloom filter 过滤(布隆过滤算法)原理
  20. Linux Cobbler批量装机

热门文章

  1. 互联网思维-标签思维(1)
  2. LeetCode 8 字符串转整数 (atoi)
  3. Flutter开发之路由跳转与传参(七)
  4. 如何使用cmd进入打印机选项_怎样用命令行方式添加打印机端口? (已解决)
  5. python基础笔记(非系统/自用/参考小甲鱼的零基础入门学习python)下
  6. 【洛谷P3846】【TJOI2007】—可爱的质数(BSGS模板)
  7. 剑指offer python版 找出数组中重复的数字
  8. 在Windows Server 2012 R2中搭建SQL Server 2012故障转移集群
  9. spring 找不到applicationContext.xml解决方法
  10. 初创公司谁来当你们的运维