我正在参与掘金会员专属活动-源码共读第一期,点击参与。
前言
我们在使用 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
语句的情况下,捕获失败的情况。