likes
comments
collection
share

【源码共读】如何优雅的处理 Promise 的错误

作者站长头像
站长
· 阅读数 36

源码地址:await-to-js

Promise解决了优雅的解决了回调地域的问题,现在已经大范围的使用Promise,但是在使用Promise的过程中,最令人头疼的就是错误处理的方式。

Promise 的错误处理方式

据我对Promise的了解,Promise的错误处理分为下面的几种方式,如果有还有其他的处理方式,欢迎大家指出。

1. try catch

try catch是最常见的错误处理方式:

try {
  await Promise.reject('error')
} catch (error) {
  console.log(error)
}

但是这种方式有一个致命的缺点,就是只能捕获同步的错误,对于异步的错误,是无法捕获的。

2. Promise.prototype.catch

Promise提供了catch方法,用于捕获Promise的错误:

Promise.reject('error').catch((error) => {
  console.log(error)
})

这种方式是我比较常用的方式,因为它不仅可以捕获同步的错误,还可以捕获异步的错误:

async function test() {
  await Promise.reject('error')
}

test().catch((error) => {
  console.log(error)
})

3. Promise.prototype.then

Promise提供了then方法,用于捕获Promise的成功和失败:

Promise.reject('error').then(
  (value) => {
    console.log(value)
  },
  (error) => {
    console.log(error)
  }
)

这种方式我放在catch的后面,是因为我很少用,有时候我感觉它很鸡肋,因为它只能处理Promise执行体的错误,而不能处理自己本身的错误:

// 这样是可以的
Promise.reject('error')
  .then(
    (value) => {
      console.log(value)
    },
    (error) => {
      console.log(error)
    }
  )

// 这样是不行的
Promise.resolve('error')
  .then(
    (value) => {
      throw new Error('error')
    },
    (error) => {
      console.log(error)
    }
  )

链式调用

上面说的几种错误处理方式都是对Promise的单次调用,怎么样处理都是可以的;

但是Promise的出现就是为了解决回调地域的问题,所以我们经常会使用链式调用的方式:

await Promise.reject('error');
await Promise.reject('error');
await Promise.reject('error');

通常我们会像上面这样,通过await来简化链式调用的负担,但是这样的话,我们对错误的处理就会变得很麻烦:

  • 统一使用一个大的try catch来捕获所有的错误
try {
  await Promise.reject('error');
  await Promise.reject('error');
  await Promise.reject('error');
} catch (error) {
  console.log(error);
}

但是这样的话,我们就无法知道是哪个Promise出现了错误,无法保证后续的Promise能够正常执行。

  • 使用Promise.prototype.catch来捕获每个Promise的错误
const errorHandle = (error) => {
  console.log(error);
};

await Promise.reject('error').catch(errorHandle);
await Promise.reject('error').catch(errorHandle);
await Promise.reject('error').catch(errorHandle);

这种方式就是我经常使用的方式,但是这样我需要为每一个Promise都写一个错误处理函数。

  • 链式处理
const await1 = async () => {
  await Promise.reject('error');
};

const await2 = async () => {
  await Promise.reject('error');
};

const await3 = async () => {
  await Promise.reject('error');
};

await1()
  .then(await2)
  .then(await3)
  .catch((error) => {
    console.log(error);
  });

这种方式其实和使用一个大的try catch是一样的,不过还可以衍生出下面的方式:

await1()
  .then(await2)
  .catch(errorHandle)
  .then(await3)
  .catch(errorHandle);

// 或者
await1()
  .then(await2, errorHandle)
  .then(await3, errorHandle)
  .catch(errorHandle);

显然这样很麻烦,然后还可以借助Promise.all或者Promise.race来简化:

Promise.all([await1(), await2(), await3()])
  .then(([data1, data2, data3]) => {
    console.log('success');
  })

当然Promise.all的特点大家都清楚,如果其中一个Promise出现错误,那么整个Promise都会失败,然后就有了Promise.race

Promise.race([await1(), await2(), await3()])
  .then(() => {
    console.log('success');
  })

这样的话,只要有一个Promise成功,那么整个Promise就会成功,弥补了Promise.all的不足。

当然还是有很多人不满足,因为懒是天性,就是不想写那么多的Promise,也不想写那么多的catch,于是就有了今天的主角await-to-js

源码分析

await-to-js的源码很简单,就是在内部对错误进行了处理,然后返回一个正确的promise

/**
 * @param { Promise } promise
 * @param { Object= } errorExt - Additional Information you can pass to the err object
 * @return { Promise }
 */
function to(promise, errorExt) {
  return promise
    .then((data) => [null, data])
    .catch((err) => {
      if (errorExt) {
        const parsedError = Object.assign({}, err, errorExt);
        return [parsedError, undefined];
      }

      return [err, undefined];
    });
}

从代码上可以看到await-to-js的使用方式很简单,只需要在to函数中传入一个Promise,然后就可以得到一个数组,数组的第一个元素是错误信息,第二个元素是成功的数据。

const [error, data] = await to(Promise.reject('error'));

它的第二个参数是一个对象,可以传入一些额外的信息,这个信息会被合并到错误信息中。

内部实现也很简单,就是我们上面讲过的错误处理方式其中一种,使用Promise.prototype.catch来捕获错误,然后返回一个正确的Promise

不过就是对返回的结果进行规范化处理,返回一个数组,第一个元素是错误信息,第二个元素是成功的数据。

使用

通过看源码我们就知道了await-to-js的使用方式,那么我们来看看它的使用场景:

const await1 = async () => {
    await Promise.reject('error');
};

// 可以忽略错误
const [, data] = await to(await1);

// 可以无视
await to(await1);
await to(await1);
await to(await1);

// 可以捕获错误
const [error, data] = await to(await1);
if (data) {
  // do something
}

// 可以类柯里化
const p1 = to(await1)
const p2 = to(p2)
const p3 = to(p3)
const [, data] = await to(p3)

怎么用自己舒服就怎么用,还可以探索别的使用方式,让操作骚起来。

总结

await-to-js的实现方式很简单,就是对一个promise进行错误处理,然后返回一个正确的promise;

但是部分人可能会对每次判空感觉很麻烦,那么可以考虑将这个方法修改一下,返回自己喜欢的方式就可以了。

例如将返回数组改为对象,通过错误码来判断是否成功,这样就可以不用每次都判空了。

或者再发挥自己过度封装的能力,将判空也封装起来,哈哈哈。

转载自:https://juejin.cn/post/7177184261257035831
评论
请登录