【不可不知】如何优雅的进行 async await 错误处理

3,562 阅读2分钟

async 函数

ES2017引入了async函数,使得异步操作和使用更加便捷。可以像写同步代码似的书写异步代码。

// 常规 Promise 写法
fetchPageData()
    .then((pageDataResponse) => {
        const { data } = pageDataResponse;
        setPageData(data);
    });

// 使用 async 写法
const getPageData = async () => {
    const pageDataResponse = await fetchPageData();
    const { data } = pageDataResponse;
    setPageData(data);
};

错误处理

虽然async函数使得异步操作简单化,但是其出现异常时,错误处理却是比较麻烦的。

  1. try...catch 错误处理:将await错误包裹在try...catch代码块中,进行异常捕获。
const getPageData = async () => {
    try {
        const pageDataResponse = await fetchPageData();
        const { data } = pageDataResponse;
        setPageData(data);
    } catch (e) {
        console.log('page data fetch error ', e);
    }
};
  1. promise reject catch 错误处理:async函数返回一个Promise对象,其内部抛出错误,会导致返回的Promise对象变为reject状态,可以通过catch函数进行错误兜底。
const getPageData = async () => {
    const pageDataResponse = await fetchPageData().catch((e) => {
        console.log('page data fetch error ', e);
    });
    const { data } = pageDataResponse;
    setPageData(data);
};

针对单个async函数使用这些错误处理还是可行,当有多个async函数需要处理时,就不是很优雅了。尤其是当针对每一个async函数的错误处理还不一致时,对于有代码洁癖的人可能就到了不能忍的程度了。

const getDisplayData = async () => {
    try {
        const data1 = await fetchData1();
        const data2 = await fetchData2();
        ...
    } catch (e) {
        console.log('error catch ', e);
    }
};

await-to-js 错误处理

文章 How to write async await without try-catch blocks in Javascript 对如何在javascript中优雅的使用asyncawait进行错误的处理?有详细的描述,其中有推荐 await-to-js 这个库,也可以通过npm安装的方式进行使用。其代码也比较简单,async函数返回的是一个promise对象,对promise对象进行一个统一的wrap处理,其wrap就包含catch异常处理。

/**
 * @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) {
        Object.assign(err, errorExt);
      }

      return [err, undefined];
    });
}

export default to;

使用 await-to-js 进行异步操作。

import to from 'await-to-js';

const fetchData1 = async () => {};
const fetchData2 = async () => {};
const fetchPageData = async () => {
    const data1 = to(fetchData1());
    const data2 = to(fetchData2());
    const [error1, res1] = data1;
    const [error2, res2] = data1;
    if (res1) {}
    if (error1) {}
    if (res2) {}
    if (error2) {}
};

进而可以对 await-to-js 进行二次封装,进行业务使用。

import to from 'await-to-js';

const awaitWrap = async <T, U = Error>(
    promise: Promise<T>,
    onSuccess?: (data: T) => void,
    onError?: () => void,
    errorExt?: object,
) => {
    const handleError = onError || defaultErrorFn;
    const handleSuccess = onSuccess || defaultSuccessFn;
    const res = await to<T, U>(promise, errorExt);
    const [data, error] = res;
    onSuccess(data);
    onError(error);
};

使用awaitWrap进行异步操作。

const fetchData1 = async () => {
    await awaitWrap(
        fetch('/data1'),
        (data) => {
            setData1(data);
        },
        (error) => {
            const { message } = error;
            toast.error(message);
        },
    );
};
const fetchData2 = async () => {
    await awaitWrap(
        fetch('/data2'),
        (data) => {
            setData2(data);
        },
        (error) => {
            const { message } = error;
            toast.error(message);
        },
    );
};

const fetchPageData = async () => {
    await fetchData1();
    await fetchData2();
    ...
};

综上,async await异常处理不仅是try...catch,通过对async await异常处理进行提取封装,也可以使得异常处理更加的优雅。