Promise 到底是什么玩意?

758 阅读3分钟

本文主要讨论一下Promise概念以及一些使用场景和如何正确使用。本文讨论的内容大概是适合对Promise的初学者,如果你已经对Promise很熟悉了,那么没有必要看。

Promisie到底是什么玩意?

这两天参加了一个线上做题面试,有一道Promise的题目,很有意思。做题的过程中让我对Promise又有了新的理解。

Promise是什么?

Prosmise这东西本质上就是一个设计的对象,根本没有什么神秘的。Promise就像一个飞去来器,扔出去,会有两种结果:
(1)成功的飞回手上
(2)飞的时候突然爆炸了,渣都没了,只听到一声响:“reject!”
现在我们直接看一个代码:

// boomerang 是一个Promise 函数,你不用关心它如何实现的
// 你可以假设它是一个平时你能用到的HTTP call 可以返回特定的内容
// 它回成功返回数据(resolve),也可能失败(reject)
const boomerang = (s = 0, type = 'resolve', data = `飞去来器${s}型号回来了`) => {
  if (type !== 'resolve') {
    return new Promise((resolve, reject) =>
      setTimeout(() => {
        reject(new Error(`${s}毫秒后爆炸`));
      }, s)
    );
  }
  return new Promise((resolve) =>
    setTimeout(() => {
      resolve(data);
    }, s)
  );
};

const world = () => {
  console.log('吃大葱');
  const a = boomerang(0);
  console.log(a);
  const b = boomerang(1000, 'reject').catch((e) => {
    return e.toString();
  });
  console.log(b);
  const c = boomerang(2000);
  console.log(c);
  console.log('吃大蒜');
};

world();

那么这段代码返回什么呢?如Prosmise不是特别熟悉,你可能会想错了。你可能会误以为会返回’吃大葱‘’ 吃大蒜‘然后后面跟着其它值是undefinied;其实不是,下面看一下返回值:

吃大葱
Promise { <pending> }
Promise { <pending> }
Promise { <pending> }
吃大蒜

Promise不是不会立即执行的吗?怎么没有返回值呢?Promise { <pending> }是什么东西?
下面我们先把Promise的基础再看一遍。Promise在JS任务队列里面是立即执行的。比如boomerang(0)这个函数,是直接排在吃大葱后面的。只是Promise的返回值是在未来返回的,如果你像这个代码一样想获得boomerang函数返回值是不行的,你只能得到return new Promise返回的结果,一个正在等待的Promise。 那么如何才能让这个代码正常工作呢?(得到a,b,c的值) 我们可以用老派的then函数一顿then()然后进行处理(不推荐,代码落后于时代而且太难看也容易出bug)。下面我们用标准的async/await重写一下:’

const world = async () => {
  console.log('吃大葱');
  const a = await boomerang(0);
  console.log(a);
  const b = await boomerang(1000, 'reject').catch((e) => {
    return e.toString();
  });
  console.log(b);
  const c = await boomerang(2000);
  console.log(c);
  console.log('吃大蒜');
};
world();

这样返回结果就对了。

吃大葱
飞去来器0型号回来了
Error: 1000毫秒后爆炸
飞去来器2000型号回来了
吃大蒜

这样的到了我们想要的结果,而且我们也对reject的函数正确地catch了错误。但是这个代码虽然可以正确执行,实际上还是有一些问题的。问题在于,b的值需要等1秒,c的值需要等2秒,整个world()执行用了3秒。

如何继续优化?

这里面有3个Promise,其实是可以同步执行的。下面我们就要引出Promise.all来对它们进行同步执行。改写代码如下:

const world = async () => {
  console.time('test');
  console.log('吃大葱');
  const [a, b, c] = await Promise.all([
    boomerang(0),
    boomerang(1000, 'reject').catch((e) => {
      return e.toString();
    }),
    boomerang(2000),
  ]);
  console.log(a, b, c);
  console.log('吃大蒜');
  console.timeEnd('test');
};
world();

看一下执行结果:

吃大葱
飞去来器0型号回来了 Error: 1000毫秒后爆炸 飞去来器2000型号回来了
吃大蒜
test: 2005.940ms

所以在实际工作的代码中,如果有多个Promise不是互相依赖的,要善用Promise.all来进行优化。你可能对于代码中没有catchPromise.all进行catch的错误抱有疑虑。其实在这里如果我们对a,b,c的函数调用进行了catch,就不必对Promise.all进行catch。这里需要加深理解。

暂时先写到这里。可能还需要写一下race什么的。