使用await-to-js更优雅的去捕获await错误

144 阅读5分钟

本文参加了由公众号@若川视野 发起的每周源码共读活动,   点击了解详情一起参与。

这是源码共读的第21期 | await-to-js 如何优雅的捕获 await 的错误

阅读本文你将理解async和await的原理,如何更优雅的捕获await的错误,结合场景使用await-to-js

源码

Git仓库:await-to-js

或者 npm i await to js

前言

理解async和await语法糖我们需要先理解它的本质生成器的工作原理

生成器

ES6新增的一种主要用于函数控制使用的 迭代器方案,比迭代器更灵活,生成器本质上就是一种特殊的迭代器

  1. 生成器也是一个函数 只是和普通函数用法不同
  2. 生成器函数通过 yield关键字控制函数的执行流程
  3. 生成器函数 需要在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这两个关键字,它简化了前面提到过的生成器使用, 异步变同步变成了一件简单的事情

  1. async和await需要配套使用
  2. 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();

错误提示

尽管使用asyncawait之后变的更加简洁, 但是还有一个问题,我们没有比较好的办法判断错误或者当这个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还需要另外做处理

image.png

在举个🌰

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("我是一个错误消息");
    }
  });
});

image.png

总结

  1. async await 本质来说就是 生成器的 语法糖
  2. 通过await-to-js 可以更方便对 await 后的Promise结果 做判断
  3. await-to-js 的本质 还是Promise的链式调用,返回一个新的结果,学好Promise很重要