Promise静态方法手搓系列——Promsie.all()

412 阅读4分钟

前言

在Promise这个es6新增的对象身上有很多神奇的方法,通常被我们用来处理一些异步代码,比如里面的.then().catch()等,除了它的实例对象上面的方法,它本身也包括了一些静态方法可以让我们处理异步任务,接下来我们就来聊聊Promise.all()

Promise.all()简介

在我们开发的时候,我们有时候需要处理一堆的异步任务,并且需要获取到它们的结果,这时候有同学可能会想到用async/await来实现,这样虽然能实现,但是还是不够优雅,我们要写很多的await。这时我们就可以使用Promise.all ,它为我们提供了一种优雅的方式来同时执行这些操作,并等待它们全部完成。

作用效果:同时处理多个异步任务,并且返回一个存放它们结果的数组。(如果当中有失败的则会直接返回失败的结果)

Promise.all(params)的参数

参数类型

{95EF0587-D5BB-4AC2-A689-A96E65DF2A27}.png

当我们看到()时,想必大家都知道这东西是个函数,而函数就离不开传参,Promise.all()这个方法的参数有点特别。我们自己定义的函数的参数通常是不会去规定类型的,而Promise.all()这个方法的参数类型是固定了的,上面的官方文档中说明了得是一个可迭代对象,接下来我们往里面分别传入一个set{}试试:

image.png

我们通过浏览器控制台的打印可以看到,传入set不报错,而传入{}就报错了,并且报错的原因是object is not iterable。在这里我们就可以知道Promise.all()方法接收的参数类型是有规定的,必须接收个有迭代器属性的参数,否则就会报错

迭代器属性是什么?

那什么是迭代器属性呢?我们来看看数组身上的一些方法:

image.png

我们可以看到底部有个属性名字叫:

image.png

这个属性就是我们所说的迭代器属性,它是一个函数,如果调用的话会返回一个迭代器。

Promise.all(params)的使用

在经过简单了解它传入参数的类型之后,接下来我们就可以学习它的使用了,上文说到它接收的参数得是一个可迭代的,我们接下来就以传入参数是数组为例子,数组中的每一项可以是同步也可以是异步,同样的我们也可以直接传入一个空数组,如果传入个空数组它会返回一个成功状态的Promise,并且值就是传入的空数组:

image.png

除了这个特点之外它还有两个非常重要的特点:

  1. 如果数组中所有的任务执行完都是fulfilled状态的Promise的话,就直接返回存放每个任务结果的数组,并且顺序和放置时是一样的
  2. 如果执行过程中出现了rejected状态的Promise则立即返回这个失败的结果。

image.png

手搓Promise.all()

相信各位在面试的时候或多或少都被手搓狠狠的折磨过,接下来我们来聊聊面试的经典题:手搓Promise.all(),在上文中简单了解了Promise.all的一些特性之后,我们可以总结出这个函数手搓主要实现的几个功能,接下来手搓我们以传入数组为例子:

  1. 判断参数类型,必须传入个迭代器。并且返回个Promise。
  2. 传入的是个空数组的话直接返回成功状态的空数组。
  3. 执行传入数组中的同步和异步任务,如果有rejected就直接返回失败原因,否则返回存放执行结果的数组。
  4. 返回数组结果必须跟传入参数时放置的位置一样
  • 我们需要判断传入的参数类型,而这一点非常简单,我们只需要使用for of来遍历执行即可实现,因为它只会遍历迭代器,不是迭代器它会直接报错。

  • 我们需要将每一项通过Promise.resolve()进行包装确保一致性处理

  • Promise.resolve()效果类似于返回一个成功状态的Promise,效果类似于下面。

image.png

接下来我们直接来看代码实现:

Promise.myAll = function (params) {
  let res, rej // 储存resolve,reject
  const p = new Promise((resolve, reject) => {
    res = resolve
    rej = reject
  })

  let i = 0
  const result = [] // 存放返回结果的数组
  for (const item of params) { // 判断是不是传入的迭代器
    const index = i
    i++
    Promise.resolve(item).then(data => { // 将每一项进行包装实现一致性
      result[index] = data // 将对应结果放入对应位置
      i--
      if (i === 0) res(result) // 所有结果都是成功,就resolve出结果数组
    }, rej)
  }

  if (i === 0) {
    res([]) // 传入的空的数组,res出去
  }

  return p
}

Promise.myAll([1, 2, Promise.reject(1)])
  .then(res => {
    console.log(res);
  })
  .catch(err => console.log(err))

当然了,除了上面这种比较容易理解的写法,还有另外一种比较精简的写法:

Promise.myAll = function (promises) {
  let result = [], count = 0
  return new Promise((resolve, reject) => {
    promises.forEach((item, i) => {
      Promise.resolve(item).then(res => {
        result[i] = res
        count += 1
        if (count === promises.length) resolve(result)
      }, reject)
    })
  })
}

  
Promise.myAll([1, 2, 3, A(), Promise.resolve(9)]).then(
  res => {
    console.log('成功', res);
  },
  rej => {
    console.log('失败', rej);
  }
)