await-to-js :22 行代码让你摆脱 async、await 中繁琐的 try、catch

1. 前言

1.1 你能学到

  1. await-to-js出现的原因
  2. await-to-js使用以及原理

2. 准备

2.1 了解作用

以下一些代码片段来自官方博客,如果有中文注释,那就是我自己写的,如有错漏之处,敬请指正~

回调地狱

要问起 Promise 解决了什么,那第一个想到的必然是**回调地狱,**像这样的东西👇

function AsyncTask() {
   asyncFuncA(function(err, resultA){
      if(err) return cb(err);

      asyncFuncB(function(err, resultB){
         if(err) return cb(err);

          asyncFuncC(function(err, resultC){
               if(err) return cb(err);

               // And so it goes....
          });
      });
   });
} 

有了 ES6Promise,上面的噩梦代码就可以简化为这样 👇

function asyncTask(cb) {

   asyncFuncA.then(AsyncFuncB)
      .then(AsyncFuncC)
      .then(AsyncFuncD)
      .then(data => cb(null, data)
      .catch(err => cb(err));
} 

实际开发中的复杂异步流程

但可能你有这样的需求:

  • 在某一步结束后,你想要获取该步中的某个值来对其进行一些操作
  • 任何一步发生了错误都能及时且恰当地给到用户确切的错误

幸好,ES7 asyncawait的出现,让逻辑处理更为干净利落:

async function asyncTask(cb) {
    const user = await UserModel.findById(1);
    if(!user) return cb('No user found');
    // 获取中间某一个的某个值,方便后面对其操作
    const savedTask = await TaskModel({userId: user.id, name: 'Demo Task'});
    
    if(user.notificationsEnabled) {
         await NotificationService.sendNotification(user.id, 'Task Created');  
    }

    if(savedTask.assignedUser.id !== user.id) {
        await NotificationService.sendNotification(savedTask.assignedUser.id, 'Task was created for you');
    }

    cb(null, savedTask);
} 

但是,我错误处理呢?

处理错误时繁琐的代码

异步调用时,由于异步函数等待 Promise ,当 Promise 遇到错误,就会抛出一个异常,并在最后被 Promise 对象的 catch 方法给捕获 —— 而使用 async 和 await,就需要使用 try + catch 来捕获错误,这会导致这样:

async function asyncTask(cb) {
  //每一处操作都需要一个 try+catch
    try {
       const user = await UserModel.findById(1);
       if(!user) return cb('No user found');
    } catch(e) {
        return cb('Unexpected error occurred');
    }

    try {
       const savedTask = await TaskModel({userId: user.id, name: 'Demo Task'});
    } catch(e) {
        return cb('Error occurred while saving task');
    }

    if(user.notificationsEnabled) {
        try {
            await NotificationService.sendNotification(user.id, 'Task Created');  
        } catch(e) {
            return cb('Error while sending notification');
        }
    }

    if(savedTask.assignedUser.id !== user.id) {
        try {
            await NotificationService.sendNotification(savedTask.assignedUser.id, 'Task was created for you');
        } catch(e) {
            return cb('Error while sending notification');
        }
    }

    cb(null, savedTask);
} 

看起来,真不太好;而如果不用,出现了错误他只会默默地退出函数,这脱离了控制,是不可以接受的

或许有一个更为整洁的解决方案~

2.2 先用一下

从 README 中获取上手相关信息

安装

npm i await-to-js --save 

使用小demo

import to from 'await-to-js';  //关键to方法
// If you use CommonJS (i.e NodeJS environment), it should be:
// const to = require('await-to-js').default;

async function asyncTaskWithCb(cb) {
     let err, user, savedTask, notification;
    //从这里可以看出来,响应经过to函数,会解析为一个数组:[错误,数据]
     [ 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');//如果有错误就退出并提示

    //...为了篇幅,省略一些

    cb(null, savedTask);
}

async function asyncFunctionWithThrow() {
  const [err, user] = await to(UserModel.findById(1));
  if (!user) throw new Error('User not found'); //没有数据就抛出错误
} 

这形式,有点像 React 的 Hook 有没有
const [data,setData] = useState()

很明显,使用to方法后,代码变得更干净了,使其更为可读与可维护。这个方法就是该库的关键所在。

3 看看 源码

3.1 环境准备

这次其实环境都不用准备了,因为关键代码真的是非常地少,总共就22行。当然,如果你想顺便研究一下测试用例的话还是可以git clone一下的

3.2 理解源码

在看22行的关键代码之前,我们可以先看一个简版的实现(来自上面提到的官方博客)

// to.js
export default function to(promise) {
   return promise.then(data => {
      return [null, data]; //数组大小为2,第一项用null占位置,也达到了 !err == true 的效果
   })
   .catch(err => [err]); // 数组大小为1,根本没有第二项
} 

await是在等待解决的承诺,那么只要参数接收原先的 Promise 再返回一个 处理过的 Promise —— 成功解析后变为一个数组,数据作为第二项,错误信息(如果有的话)作为第一项。
利用 Promise 可以巧妙地达到该效果:

  • 成功:返回[null, data]
  • 失败:返回[err]

现在来看库中的代码,用了 TS ,如果你还没有学过 TS,那我建议你去学一下,不过这里没学的话也没关系,也就是约束以及告知开发者该处要用什么类型的数据罢了,这里我会告诉你这代码有什么作用

你也可以自己build一下,看打包后 JS 文件

/**
 * @param { Promise } promise
 * @param { Object= } errorExt - Additional Information you can pass to the err object
 * @return { Promise }
 */
export function to<T, U = Error> ( // T:Promise成功返回的数据类型,U:错误时返回类型,默认为Error
  promise: Promise<T>,	//第一个参数只接受 Promise 对象
  errorExt?: object			// 第二个参数是可选的,是一个错误信息的扩展 ): Promise<[U, undefined] | [null, T]> { //返回的类型有两种:失败/成功
  return promise
    .then<[null, T]>((data: T) => [null, data])  //成功
    .catch<[U, undefined]>((err: U) => {		//如果有错误
      if (errorExt) {												//如果有错误信息的扩展
        const parsedError = Object.assign({}, err, errorExt); //就把他和错误信息一起合到一个空对象上
        return [parsedError, undefined];
      }

      return [err, undefined];
    });
}

export default to; 

undefine & null

这里我一开始有点好奇的是,为什么失败的时候返回的是[err,undefined]而不是[err,null]呢?而成功的时候返回[null,data]而不是[undefined],null]呢?
要说最终要实现的效果,比如判定是否存在错误信息、判定有没有返回的数据,似乎也没有什么影响。

//没有返回数据
if(!undefined)console.log(1) //1
//没有捕获到错误
if(!null)console.log(1) //1
!null === !undefined  //true 

看样子需要达到的效果相同,那么这里又是出于什么样的考虑来选用这两个数据类型呢?
或许与 typeof有关?

typeof null //'object'
typeof errorExt//'object' 

为了这里的形式一致,所以在没有错误的时候让 null 代替他的位置?
但这样为什么返回错误时就用 undefined 代表空值呢?


或许与这两个数据类型本身的意义有关?
null表示没有对象,即该处不应该有值。如果一切正常,错误信息自然是没有的——我们期望的就是一切正常,他本来就应该没有
undefined 表示缺少值,就是
此处应该有一个值
,但是还没有定义。我们当然是期望可以得到响应的数据的,但此时出现了一些问题,导致本来应该有值的地方,丢失了值

以上都是个人推测,也没有找到什么依据,欢迎讨论~

3.3 测试样例

官方的测试代码:可以从中了解到使用to更多更具体的效果,以及学习到测试样例的书写分类:

  • 单纯一个 resolved 情况返回数据
  • rejected 情况返回错误
  • 有扩展错误信息的情况
    • 注意看错误时返回的类型设置
  • 开发者没有设置类型时,TS 自己推导类型够不够用

4. 学习资源

5. 总结 & 收获

  • 学会如何更优雅地捕获错误,使代码更为简洁确实是令人愉悦
  • 接收一个 Promise再返回 处理过的Promise,最后以数组形式来存储错误/数据来方便判断,这方法真是巧妙啊
  • 9
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值