深入理解Promise.all

深入理解Promise.all

了解es6的Promise的人应该都听过Promise.all,而且应该是大多数的人都用过Promise.all这个方法。首先Promise.all可以将多个Promise实例包装成一个Promise实例。

let p = Promise.all([p1, p2, p3])

Promise.all方法可以接受一个数组作为参数,数组中的每一项都是一个Promise的对象实例(如果不是,就会先调用Promise.reslove方法,将参数转化为Promise对象实例,再进行下一步的处理)。Promise.all方法的参数不一定是数组,但是必须具有Iterator接口。

  1. 只有数组里每个Promise的状态都编程了Fulfilled,Promise.all的状态才会变成Fulefilled,然后将每一个状态发返回的值,组装程一个数组返回给后面的回调函数。
  2. 只要数组里面有一个Promise的状态是Rejected,Promise.all的状态就会变成Rejected。这个时候第一个被Rejected的实例的结果会传递给后面你的回调。
  3. 如果作为参数的Promise实例自身定义了catch方法,那么他被Rejected时并不会被Promise.all的catch的方法给接受。

Promise.all队列中异步的执行顺序

Promse.all在处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个ajax的数据回来以后才正常显示,在此之前只显示loading图标。但是针对于Promse.all中的多个异步的执行顺序是并行的还是串行的呢?这边我通过一个例子来证明一下:

function promise1 () {
  return new Promise(resolve => {
    console.log('promise1 start');
    setTimeout(() => {
      console.log('promise1 over');
      resolve();
    }, 100);
  })
}
function promise2 () {
  return new Promise(resolve => {
    console.log('promise2 start');
    setTimeout(() => {
      console.log('promise2 over');
      resolve();
    }, 90);
  })
}

Promise.all([promise1(), promise2()])

我们看上面的例子,如果说,Promise.all中的方法是串行的话,那么输出的顺序应该是 promise1 start -> promise1 over -> promise2 start -> promise2 over。但是实际的输出的顺序却不是这样的。具体的结果如下图:

这也就说明,在Promise.all中的执行的顺序其实应该是并行的(其实这边说并行是有问题的)。

如何实现Promise.all

在我们都知道了Promise.all的基本原理和串并行的关系之后,我们就来动手实现一下这个Promise.all的内容。

Promise.all = function (promise) {
  return new Promise((resolve, reject) => {
      let index = 0
      let result = []
      if (promise.length === 0) {
          resolve(result)
      } else {
          function processValue(i, data) {
              result[i] = data
              if (++index === promise.length) {
                  resolve(result)
              }
          }
          for (let i = 0; i < promise.length; i++) {
              Promise.resolve(promise[i]).then((data) => {
                  processValue(i, data)
              }, (err) => {
                  reject(err)
                  return
              })
          }
      }
  })
}

我们来简单的分析一下,首先我们知道,Promise.all返回的结果是一个Promise对象,那么,我们的方法也返回这样的一个对象;接着,我们上面讲到了,有关Promise.all的内部是并行执行的因此们这边不用等待上一个任务执行完再去执行,而是可以直接将任务塞到执行队列中,当每一个reslove之后,我们将最终的结果给reslove掉,如果其中有一个存在问题,我们就直接reject掉。因此,就形成了我们上面的内容。

Promise.all并发限制

现在我们清楚了Promse.all在处理多个异步处理时非常有用。但是如果说这个异步特别多的时候会是怎样的结果呢。比如说我们要加载100个接口的数据,当这个100个接口的数据加载完成之后我们在给返回一个加载完成。如果我们按照Promise.all的思路去写的话,我们会这么写:

let urls = [
  'https://www.api.com/images/getData1',
  'https://www.api.com/images/getData2',
  'https://www.api.com/images/getData3',
  'https://www.api.com/images/getData4',
  'https://www.api.com/images/getData5',
  'https://www.api.com/images/getData6',
   ……
   'https://www.api.com/images/getData100'
];
function loadData(url) {
  return new Promise((resolve, reject) => {
      $.get(url, result => {
        resolve(result)
      })
  })
};

let allList = []
urls.forEach(url => allList.push(loadData(url)))
Promise.all(allList).then(() => {
  console.log('success')
})

我们想象一下,如果我们一下给后台同时发送1000个请求,那么后台能够扛得动么,对于这样的问题我们需要怎么去解决呢,这个时候我们会想根据Promise.all的原理,将Promise.all的结果改装成串行执行的,如下:

Promise.asyncAll = function (promise) {
  return new Promise((resolve, reject) => {
      let index = 0
      let result = []
      if (promise.length === 0) {
          resolve(result)
      } else {
        function runPromise () {
          if (index === promise.length) {
            resolve(result);
          } else {
            Promise.resolve(promise[index]).then(data => {
              result[index] = data;
              ++index;
              runPromise()
            }, err => {
              reject(err)
            });
          }
        }
        runPromise()
      }
  })
}

这样就不容易出现高并发的问题,但是新的问题又来了,我们这样操作的话,其实我们的执行效率非常的低。假如说我们每个请求是1s这样的100个请求的话所用的时间就是100s,我们要等很久。那么面对这个问题我们怎么去处理呢,其实我们可以考虑一下,一次并行请求5个,这样的话,对服务器的压力相对来说会减少一些,而且等待请求完成的时间也会相对应的减少一些。这个时候我们就依赖于我们上面的Promise.all的方法来进行实现:

Promise.asyncStep = function (promises) {
  return new Promise((resolve, reject) => {
    let index = 0
    let stepCount = 5
    let result = []
    let count = promise.length
    if (promise.length === 0) {
        resolve(result)
    } else {
      function runPromise () {
        if (index === promise.length) {
          resolve(result);
        } else {
          stepCount = Math.min(count, stepCount)
          for (let i = 0; i < stepCount; i++) {
            --count;
            Promise.resolve(promise[index]).then(data => {
              result[index] = data;
              ++index;
            }, err => {
              reject(err)
            });
          }
          runPromise();
        }
      }
      runPromise()
    }
  })
}
posted @ 2020-01-09 00:29  大耳朵秃秃  阅读(4622)  评论(1编辑  收藏  举报