await-to-js 源码分析,体验一把捕获异常的优雅

举报
叶一一 发表于 2023/08/26 09:36:25 2023/08/26
【摘要】 前言await-to-js 的源码较少,代码较少的情况,可以尝试采用 3W 方法进行源码分析,帮助快速了解它的实际应用场景。阅读本文,会有以下收获:了解 await-to-js 库能解决什么问题。分析 await-to-js 库是如何解决问题。摸索 await-to-js 库的实际使用场景。Why:await-to-js 诞生的契机打开它的 github 地址,有很简单的一句介绍:Async...

前言

await-to-js 的源码较少,代码较少的情况,可以尝试采用 3W 方法进行源码分析,帮助快速了解它的实际应用场景。

阅读本文,会有以下收获:

  • 了解 await-to-js 库能解决什么问题。
  • 分析 await-to-js 库是如何解决问题。
  • 摸索 await-to-js 库的实际使用场景。

Why:await-to-js 诞生的契机

打开它的 github 地址,有很简单的一句介绍:

Async await wrapper for easy error handling

这句话的意思是:

Async await 包装器,便于错误处理

等等,async await 怎么捕获异常来着。

常规异常捕获

一般情况下,await 命令后面是一个 Promise 对象,返回该对象的结果。

如果我们希望捕获 Promise 中的错误, 这个时候需要将 await 放在try...catch结构里面。

同时,这样做,无论前面的 await 是否成功,都不会影响后续的功能。

功能设计

该功能设计源自源码的 examples 文件中提供的案例,我将它改成了使用 await-to-js 前的写法。

  • UserModel:提供了 findById 方法,是一个 Promise,它主要做了两件事,如果 userId 存在,则返回 userObjet 对象,如果 userId 不存在,则用 reject 抛出错误。
  • asyncTask: 两个参数,userId 和结果返回函数。使用async / await 处理异步操作 UserModel,将 await 放在 try...catch 结构里面。如果 UserModel 正常,则判断是否查到了 userId,如果 UserModel 阻塞,也不影响后面代码的执行。
  • 调用 asyncTask 的时候,第一个参数传 null 。
const UserModel = {
  findById: userId => {
    return new Promise((resolve, reject) => {
      if (userId) {
        const userObjet = {
          id: userId,
          notificationsEnabled: true,
        };

        return resolve(userObjet);
      }

      reject('Data is missing');
    });
  },
};

/**
 * 异步任务
 */
async function asyncTask(userId, cb) {
  try {
    const user = await UserModel.findById(userId);
    if (!(user && user.id)) return cb('No user found');
  } catch () {;
  }
	return cb('前面的 await 可能失败了');
}

asyncTask(null, (err, newTask) => {
  console.log('fail');
  console.log(err);
  console.log(newTask);
});

运行结果

因为 userId 的值为 null,所以 UserModel.findById 抛出了异常,而我在 catch 中没有做任何处理,所以后面的代码正常运行,将最后的代码处理结果抛给了 cb 函数。

所以,最终打印 cb 里的结果:

捕获异常

如果需要进行异常的捕获,在 catch 中将错误返回即可。

async function asyncTask(userId, cb) {
  try {
    const user = await UserModel.findById(userId);
    if (!(user && user.id)) return cb('No user found');
  } catch (e) {
    return cb(e);
  }
  return cb('前面的 await 可能失败了');
}

运行结果

此时返回的报错内容为: Data is missing


契机

上面的例子中,虽然代码运行正常,也可以进行异常的捕获。

但是有个问题,如果想捕获不同场景下的异常,需要将每个场景都放到 try...catch 结构中,这样代码会显得有些冗余。

能否,将 try...catch 结构封装起来,直接返回结果?

这不,await-to-js 就诞生了么。

What:如何优雅捕获异常

开头说源码内容少,并不是说说看,它是真的少。

核心源码

源码用的 TypeScript,因为代码量不多,所以没有用过 TypeScript 的开发者,也可以很顺畅的阅读。

/**
 * @param { Promise } promise
 * @param { Object } errorExt - Additional Information you can pass to the err object
 * @return { Promise }
 */
export function to<T, U = Error> (
  promise: Promise<T>,
  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;

不过,想看用 JavaScript 写的也不难,将项目 build 一下,就能得到。顺便在上面加了些注释:

/**
 * @param { Promise } promise
 * @param { Object } errorExt - Additional Information you can pass to the err object
 * @return { Promise }
 */
function to(promise, errorExt) {
    return promise
        .then(function (data) { 
        	// 返回数组值:第一个元素是错误信息(只有报错时有值),第二个元素是数据信息(只有成功时有值)
          return [null, data]; 
        }).catch(function (err) {
      	// =>true: 如果添加了额外的错误描述,则和错误描述合到一个空对象上
        if (errorExt) {
            var parsedError = Object.assign({}, err, errorExt);
            return [parsedError, undefined];
        }
        return [err, undefined];
    });
}

export { to };
export default to;

这里需要注意下面几点:

  • errorExtObject 类型。
  • Object.assign 在进行拷贝的时候,会把非 Object 类型转成 Object 类型。
const err1 = 'time';
const err2 = 's';

const task = Object.assign({}, err1, err2);
console.log(task); // { '0': 's', '1': 'i', '2': 'm', '3': 'e' }

疑问

关于 errorExt 我是有疑问的,它的结果到底怎么回显?

源码中,promise 中的 reject 抛出的都是字符串。上面提到了,Object.assign 会将字符串转成 Object 类型,得到的结果不是很直观。

来个例子看看运行结果:

async function asyncTask(userId, cb) {
  let err, user, savedTask, notification;
  [err, user] = await to(UserModel.findById(userId), { errorExt: ' ext error' });
  if (err) return cb(err);

  cb(null, savedTask);
}
asyncTask(null, (err, newTask) => {
  console.log('fail');
  console.log(err);
  console.log(newTask);
});

运行结果

这个结果并不直观

想要更直观的内容,只能将结果进行一次特殊处理,比如对象循环,只展示 value 值

if (err) {
    let errList = Object.values(err);
    let errStr = errList.join('');
    return cb(errStr);
  }

运行结果

这次打印的内容直观多了。

关于疑问的部分,仅个人想法,欢迎留言讨论

示例演示

还是上面的例子,这次使用 await-to-js 捕获异常。只需要一行代码,便可轻松捕获异常。

import to from '../../dist';

async function asyncTask(userId, cb) {
  let err, user, savedTask, notification;
  [err, user] = await to(UserModel.findById(userId));
	// 直接捕获异常
  if (err) return cb(err);
}

asyncTask(null, (err, newTask) => {
  console.log('fail');
  console.log(err);
  console.log(newTask);
});

运行结果

How:使用场景小结

不仅可以直接捕获异常,还可以根据数据的值进行特殊的逻辑处理,帮助完成一些复杂逻辑。

async function asyncTask(userId, cb) {
  let err, user, savedTask, notification;
  [err, user] = await to(UserModel.findById(userId));
  if (!(user && user.id)) return cb('No user found');

  cb(null, savedTask);
}

运行结果

在上面的代码中,根据得到的 user 的值,进行判断,在实际开发中的实用性更强一些。


总结

await-to-js 源码读完,个人感觉实际用起来,代码会写的比较优雅。而且它的源码内容不多,简单已读

简单总结一下本文收获:

1、使用 await-to-js,可以帮助改善异步捕获异常的冗余写法。

2、源码的代码量不多,可以作为工具类放到项目中使用。

3、在日常开发中,可以留意一下类似的功能改造,减少重复写法。


作者:非职业「传道授业解惑」的开发者叶一一
简介:「趣学前端」、「CSS畅想」系列作者,华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。
如果看完文章有所收获,欢迎点赞👍 | 收藏⭐️ | 留言📝。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。