本文已参与「新人创作礼」活动,一起开启掘金创作之路。
本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
【若川视野 x 源码共读】第21期 | await-to-js 如何优雅的捕获 await 的错误 点击了解本期详情一起参与。
本文涉及
官方博客:blog.grossman.io/how-to-writ…
分析库
先看ReadMe.md
这个库是用来优雅的处理try...catch错误处理
在官方博客中提到回调地狱,使用了Promise来解决,并配合ES6的async,await的写法,增加了代码的可读性。如果我们需要捕获Promise异常时,需要
async function asyncTask(cb) {
try {
const user = await UserModel.findById(1)
if (!user) return cb('No user found')
} catch (e) {
return cb('Unexpected error occurred')
}
try {
const savedTask = await TaskModel({ userId: user.id, name: 'Demo Task' })
} catch (e) {
return cb('Error occurred while saving task')
}
//...省略
cb(null, savedTask)
}
存在的问题:
-
多个
try...catch...重复,可读性较差 -
如果不定义
catch函数,错误会抛出并且自动退出函数
作者的解决方案是借鉴了go的写法:
data, err := db.Query("SELECT ...")
if err != nil { return err }
import to from './to.js';
async function asyncTask() {
let err, user, savedTask;
[err, user] = await to(UserModel.findById(1));
if(!user) throw new CustomerError('No user found');
[err, savedTask] = await to(TaskModel({userId: user.id, name: 'Demo Task'}));
if(err) throw new CustomError('Error occurred while saving task');
if(user.notificationsEnabled) {
const [err] = await to(NotificationService.sendNotification(user.id, 'Task Created'));
if (err) console.error('Just log the error and continue flow');
}
}
代码可读性更强,我们看看源码是怎么实现的
源码分析
/**
* @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];
});
}
源码非常简短,就是将Promise封装了一层
成功 return [null,data],失败return [err,undefined]
用法
interface ServerResponse {
test: number;
}
const p = Promise.resolve({test: 123});
const [err, data] = await to<ServerResponse>(p);
console.log(data.test);
封装之后跟hooks的用法相似,代码更加简洁,可读性增加
测试用例
import { to } from "../src/await-to-js";
describe("Await to test", async () => {
// 成功返回结果 return <null,number>
it("should return a value when resolved", async () => {
const testInput = 41;
const promise = Promise.resolve(testInput);
const [err, data] = await to<number>(promise);
expect(err).toBeNull();
expect(data).toEqual(testInput);
});
it("should return an error when promise is rejected", async () => {
// 失败处理结果 return <Error,undefined>
const testInput = 41;
const promise = Promise.reject("Error");
const [err, data] = await to<number>(promise);
expect(err).toEqual("Error");
expect(data).toBeUndefined();
});
it("should add external properties to the error object", async () => {
// 失败处理结果 return 自定义类型的error
const promise = Promise.reject({ error: "Error message" });
const [err] = await to<string, { error: string; extraKey: number }>(promise, {
extraKey: 1,
});
expect(err).toBeTruthy();
expect((err as any).extraKey).toEqual(1);
expect((err as any).error).toEqual("Error message");
});
it("should receive the type of the parent if no type was passed", async () => {
// 没有设置类型声明时,自动推导
let user: { name: string };
let err: Error;
[err, user] = await to(Promise.resolve({ name: "123" }));
expect(user.name).toEqual("123");
});
});
总结
这个库给处理Promise异常提供了一种简洁优雅的用法,通过封装一个hooks来增加代码的可读性,作者从go的使用借鉴到js上,这种知识的迁移能力和意识也是值得我们学习的。
最后,非常感谢若川大佬的源码共读活动,拓宽了个人的视野和知识深度,这种能力也潜移默化的影响了个人的技术能力。