本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
这是源码共读的第21期 | await-to-js 如何优雅的捕获 await 的错误
阅读本文你将理解async和await的原理,如何更优雅的捕获await的错误,结合场景使用await-to-js
源码
Git仓库:await-to-js
或者 npm i await to js
前言
理解async和await语法糖我们需要先理解它的本质生成器的工作原理
生成器
ES6新增的一种主要用于函数控制使用的 迭代器方案,比迭代器更灵活,生成器本质上就是一种特殊的迭代器
- 生成器也是一个函数 只是和普通函数用法不同
- 生成器函数通过 yield关键字控制函数的执行流程
- 生成器函数 需要在function后面加一个*号,最终返回值是一个Generator(生成器)
自上而下的函数流程
//控制函数的执行流程
function foo() {
const value1 = 100;
console.log(value1);
//在这里暂停函数 而不是靠return直接终止函数
const value2 = 200;
console.log(value2);
const value3 = 300;
console.log(value3);
}
// 100 200 300
如果我们想要暂停函数的执行,比如 在输出100后暂停,然后等待我们的继续调用怎么做?
生成器函数
function* foo1() {
const value1 = 100;
//如果我们想返回某个值
yield value1;
const value2 = 200;
yield value2;
const value3 = 300;
yield value3;
console.log("函数执行结束");
}
const generator = foo1();
console.log(generator.next()); //得到第1个结果 {value: 100, done: false}
console.log(generator.next()); //得到第2个结果 {value: 200, done: false}
console.log(generator.next()); //得到第3个结果 {value: 300, done: false}
console.log(generator.next()); //函数执行结束 返回 {value: undefined, done: true}
结合异步
function requestData(str) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(str);
}, 1000);
});
}
//promise嵌套回调
requestData("test").then(res=>{
requestData(res+"aaa")
}).then(res=>{
requestData(res+"bbb")
}).then(res=>{
console.log(res)
})
//生成器实现
function* getData() {
const res1 = yield requestData("why"); //next返回值是promise
const res2 = yield requestData(res1 + "aa");
const res3 = yield requestData(res2 + "bb");
}
const generator = getData();
//迭代器对象 value是具体的返回值
//返回值是一个promise
generator.next().value.then((res) => {
console.log(res); //why
generator.next(res).value.then((res) => {
console.log(res); //whyaa
generator.next(res).value.then((res) => {
console.log(res); //whyaabb
});
});
});
到这里可能就已经大体能理解await和async的原理,通过yield控制函数的执行,将本来异步的请求或者方法 改变为同步的过程
语法糖
async和await
ES6后 提供async 和 await这两个关键字,它简化了前面提到过的生成器使用, 异步变同步变成了一件简单的事情
- async和await需要配套使用
- await 衔接的是一个Promise对象,返回的是Promise的结果值
async function getData() {
const res1 = await requestData("why");
console.log(res1); //why
const res2 = await requestData(res1 + "aa");
console.log(res2); //whyaa
const res3 = await requestData(res2 + "bb");
console.log(res3); //whyaabb
}
getData();
错误提示
尽管使用async和await之后变的更加简洁, 但是还有一个问题,我们没有比较好的办法判断错误或者当这个Promise是reject的情况下的结果
function requestData(str) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (!str) reject("str is undefined");
resolve(str);
}, 1000);
});
}
async function getData() {
const res1 = await requestData();
console.log(res1); //why
}
getData();
因为 await仅仅是把Promise的结果捕获并进行返回, 我们对于判断这个Promise到底是resolve还是reject还需要另外做处理
在举个🌰
const findByOne = (id) => {
return new Promise((resolve, reject) => {
if (id) resolve(id);
reject("id is undefined");
});
};
async function getData() {
const res1 = await findByOne();
console.log(res1); //why
}
getData();
此时 res1 的结果是一个抛出的异常消息: id is undefined
使用await-to-js
await-to-js则解决了这个问题,它把结果封装成了一个包含失败结果和成功结果的数组,我们可以更好的去做判断
使用
import to from 'await-to-js';
const findByOne = (id) => {
return new Promise((resolve, reject) => {
if (id) resolve(id);
reject("id is undefined");
});
};
async function getData() {
const res1 = await to(findByOne());
console.log(res1);
}
getData(); // ['id is undefined', undefined]
经过to函数包装返回的结果会变为一个数组,下标0的是失败的结果,1为成功的结果
结合使用
const findByOne = (id) => {
return new Promise((resolve, reject) => {
if (id) resolve(id);
reject("id is undefined");
});
};
async function getData(id, errCallback) {
let err, res;
[err, res] = await to(findByOne());
if (err) errCallback("findByOne No User ID");
}
getData(1, (err) => {
console.log(err); // findByOne No User ID
});
源码阅读
/**
* @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;
写出对应的JS版本方便理解
function to(promise, errorExt) {
// 返回promise的调用
return promise
.then((res) => [null, res])
.catch((err) => {
if (errorExt) {
const parsedError = Object.assign({}, err, errorExt);
return [parsedError, undefined];
}
return [err, undefined];
});
}
to函数的实现本质其实就是对Promise的链式再次调用,通过对传入的promise再次调用,并将结果改变为数组的形式再进行返回,不管是then还是catch 其本质返回的都是一个新的Promise
此时通过to函数包装后 await拿到的结果 就是我们想要的 数组形式了
编写测试
import { to } from "../src/await-to-js";
describe("await-to-js Test", async () => {
it("测试resolve状态", async () => {
const promise = Promise.resolve(1);
const [err, res] = await to<number>(promise);
expect(err).toBeNull(); //err为null
expect(res).toEqual(1); //data为1
});
it("测试reject状态", async () => {
const promise = Promise.reject("失败");
const [err, res] = await to<number>(promise);
expect(err).toEqual("失败"); //err为 失败
expect(res).toBeUndefined(); //res 为一个undefined
});
it("测试合并的错误信息", async () => {
const promise = Promise.reject({ msg: "我是一个错误消息" });
const [err] = await to<string, { msg: string; extraKey: number }>(promise, { extraKey: 1 });
expect(err).toBeTruthy();
if (err) {
expect(err.extraKey).toEqual(1);
expect(err.msg).toEqual("我是一个错误消息");
}
});
});
总结
- async await 本质来说就是 生成器的 语法糖
- 通过await-to-js 可以更方便对 await 后的Promise结果 做判断
- await-to-js 的本质 还是Promise的链式调用,返回一个新的结果,学好Promise很重要