- 本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
- 这是源码共读的第21期,链接:juejin.cn/post/708310…
看源码还是需要知道这个代码主要是干什么的,解决了什么样的问题,这样才可以深入的了解源码。
await-to-js的来历
从官方的角度来说它就是一个async、await 包装器,便于错误处理,那么为什么要这样做呢? 首先看一下官方的例子:
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....
});
});
});
}
才开始我们写异步代码是像上面一样的,造成了地狱回调的问题,导致代码维护困难,那么后面有了promise,我们的代码变成了下面的:
function asyncTask(cb) {
asyncFuncA.then(AsyncFuncB)
.then(AsyncFuncC)
.then(AsyncFuncD)
.then(data => cb(null, data)
.catch(err => cb(err));
}
上面的代码简洁了很多而且维护性也好了很多,本身存在all方法,所以可以支持多个并发请求,获取并发请求中数据,解决了地狱回调的问题。相比较于上面的回调地狱,使用Promise可以帮助我们让代码只在纵向发展,并且提供了处理错误的回调。显然优雅了很多。不过就算Promise已经这么优秀了,可是依然存在两个美中不足的地方。
- 不够同步(代码依然会纵向延伸)。
- 不能很方面的给每一次异步操作都进行错误处理。
当然在我看来存在的问题就是如果不设置回调函数,promise内部抛出的错误,不会反应给外部。另外一个就是存在多个异步依赖调用的时候,后面的执行依赖前面的结果,这个时候一个是不断往下传递,或设置全局变量来接受,这样可能就比较复杂。
这就带来了解决方案async/await,那么它的代码就可以改写为:
function async asyncTask(cb) {
const asyncFuncARes = await asyncFuncA()
const asyncFuncBRes = await asyncFuncB(asyncFuncARes)
const asyncFuncCRes = await asyncFuncC(asyncFuncBRes)
async/await看起来就相对简洁明了很多了,并且async返回的还是Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。 如果为上面的代码加上异常处理,那么代码就变成如下:
function async asyncTask(cb) {
try {
const asyncFuncARes = await asyncFuncA()
} catch(error) {
return new Error(error)
}
try {
const asyncFuncBRes = await asyncFuncB(asyncFuncARes)
return new Error(error)
}
try {
const asyncFuncCRes = await asyncFuncC(asyncFuncBRes)
} catch(error) {
return new Error(error)
}
}
看上去有了异常处理,代码基本就完善了,但是为每一个await都添加try/catch这样就显得太过于臃肿了,那么要简化就需要提到今天的主角await-to-js了。
await-to-js用法
import to from 'await-to-js';
function async asyncTask() {
const [err, asyncFuncARes] = await to(asyncFuncA())
if(err) throw new (error);
const [err, asyncFuncBRes] = await to(asyncFuncB(asyncFuncARes))
if(err) throw new (error);
const [err, asyncFuncCRes] = await to(asyncFuncC(asyncFuncBRes)
if(err) throw new (error);
}
上面的用法是不是就简洁多了,没有了try/catch,看着也方便了很多,其实就是使用to这个函数将try/catch替换掉了,返回了两个值。
await-to-js源码分析
// src/await-to-js.ts
/**
* @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;
上面的代码是ts的,代码很简单,需要两个参数一个是promise类型的,一个error类型的,其实这里要求第一个参数是promise类型的就和上面我所讲的async函数返回的是一个promise对象对应上了。有可能看起来很奇怪,那么写成js版本就是:
export function to(promise, errorExt) {
return promise
.then((data) => [null, data]) // 成功,返回[null,响应结果]
.catch((err) => {
if (errorExt) {
const parsedError = Object.assign({}, err, errorExt);
return [parsedError, undefined]; // 失败,返回[错误信息,undefined]
}
return [err, undefined];
});
}
export default to;
上次之所以要传入错误对象,其实就是这玩意拿来用户自定义错误信息的,通过Object.assign将正常返回的error和用户自定义和合并到一个对象里面供用户自己选择。
总结
其实源码很少,也不难,主要里面牵扯的前因后果很多,里面主要还是对promise的理解和使用,如果对promise认识足够深入,其实这一整套就很简单了,所以我基本又把阮一峰老师的promise,async异步编程刷了一遍,毕竟现在很多源码和项目基本都牵扯了上面的两个知识点,如果不弄明白或许很多源码都将会都不明白。