我正在参与掘金会员专属活动-源码共读第一期,点击参与。
前言
我们在使用 async...await 进行异步编程的时候,如果当前异步操作失败了,为了不影响后面代码的执行,就需要将 await 语句放在 try...catch 语句里面。
那么整个程序上,就可能会有很多个 try...catch语句 ,代码上不美观,可读性也很差。
所以 await-to-js 库就是为了解决这个问题,让代码能够优雅地捕获 await 异常。
GitHub 地址:github.com/scopsy/awai…
也可以用 github1s 访问,速度更快:github1s.com/scopsy/awai…
使用方法
在进行源码分析之前,我们需要熟悉它的用法。
首先来回顾一下,常规捕获 await 异常是怎么样的:
const UserModel = {
findById: (userId) => {
return new Promise((resolve, reject) => {
if (userId) {
const userObjet = {
id: userId,
notificationsEnabled: true,
};
resolve(userObjet);
}
reject("Data is missing");
});
},
};
async function asyncTask() {
try {
const user = await UserModel.findById(null);
} catch (error) {
console.log(error); // Data is missing
}
}
asyncTask();
UserModel.findById(null) 可以看作是我们平时在项目中调用的异步操作的 api 函数。一般情况下, await 语句后面跟的是 Promise 对象,并返回该对象的结果,如果要捕获 reject 返回的错误,就需要用到 try...catch 中的 catch 来接收这个错误,让后面的代码能够继续运行下去,否则的话,就会抛出异常,整个程序停止运行。
再来看看 await-to-js 是怎么捕获 await 异常的
首先下载 await-to-js 库到项目依赖中:
npm i await-to-js --save
引入并使用 await-to-js:
import to from 'await-to-js';
const UserModel = {
findById: (userId) => {
return new Promise((resolve, reject) => {
if (userId) {
const userObjet = {
id: userId,
notificationsEnabled: true,
};
resolve(userObjet);
}
reject("Data is missing");
});
},
};
async function asyncTask() {
const [err, data] = await to(UserModel.findById(null));
console.log(err); // "Data is missing"
console.log(data); // undefined
}
asyncTask();
将调用异步函数(UserModel.findById(null))后返回的结果当作 await-to-js(to) 函数的参数传入,然后用有两个元素的数组接收 to 函数返回的结果,第一个元素 err 是失败返回的结果,第二个元素是 data 成功后返回的结果。
可见 await-to-js 只用了一行代码就完成了 5 行代码的捕获异常。
再来看看,成功返回结果的情况:
async function asyncTask() {
const [err, data] = await to(UserModel.findById(1));
console.log(err); // undefined
console.log(data); // {id: 1, notificationsEnabled: true}
}
asyncTask();
非常完美地获取到了 resolve 返回的结果。
源码分析
await-to-js 是 TypeScript 代码,代码量比较少,不到 15 行,即使没有使用过 TypeScript 的开发人员,也能看懂其中的奥妙。
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];
});
}
也可以在编译器的终端输入 npm run build 或 yarn build,将 TypeScript 代码编译成 JavaScript 代码:
function to(promise, errorExt) {
return promise
.then(function (data) {
return [null, data];
})
.catch(function (err) {
if (errorExt) {
var parsedError = Object.assign({}, err, errorExt);
return [parsedError, undefined];
}
return [err, undefined];
});
}
源码的核心思想是:异步操作的成功结果通过 then 方法接收,失败结果通过 catch 方法接收,两个方法都用 return 语句将结果返回,这样的话,无论是 then 方法还是 catch 方法返回的新 Promise 对象的状态都是 fulfilled (成功的状态),因此能够被 await 正常接收。
对源码的疑问
关于 catch 方法里的 errorExt,不是特别明白,因为 err 可能是字符串,而 errorExt 是对象(TypeScript 源码中声明了 errorExt 是对象类型),那... Object.assign({}, err, errorExt) 语句是什么意思呢?字符串能够和对象进行合并吗?我们来试试看:
const err = 'failed';
const errorExt = {msg: 'error'}
Object.assign({}, err, errorExt)
得到的结果是:
{
0: "f",
1: "a",
2: "i",
3: "l",
4: "e",
5: "d",
msg: "error"
}
使用 Object.assign 将字符串和对象进行合并时,会把字符串转换为——键为字符的索引,值为字符的对象。虽然结果上看字符串可以和对象合并,但是合并出来的结果...呃呃呃,还是看不懂。
个人觉得应该对 err 和 errorExt 进行数据类型判断,如果都是对象类型,那么才进行合并:
if (errorExt && typeof err === 'object') {
var parsedError = Object.assign({}, err, errorExt);
return [parsedError, undefined];
}
以上仅个人想法,欢迎到评论区留言讨论。
总结
await-to-js 主要运用了 then 和 catch 方法来接收结果,并通过 return 返回处理后的结果,这样新 Promise 对象的状态都是 fulfilled,结果被 await 正常接收,这就能够在不使用 try...catch 语句的情况下,捕获失败的情况。