第9题 promise解决并发请求

    var urls = [
      'http://jsonplaceholder.typicode.com/posts/1',
      'http://jsonplaceholder.typicode.com/posts/2', 
      'http://jsonplaceholder.typicode.com/posts/3', 
      'http://jsonplaceholder.typicode.com/posts/4',
      'http://jsonplaceholder.typicode.com/posts/5', 
      'http://jsonplaceholder.typicode.com/posts/6', 
      'http://jsonplaceholder.typicode.com/posts/7', 
      'http://jsonplaceholder.typicode.com/posts/8',
      'http://jsonplaceholder.typicode.com/posts/9', 
      'http://jsonplaceholder.typicode.com/posts/10'
    ]
    
    function loadDate (url) {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()
        xhr.onload = function () {
          resolve(xhr.responseText)
        }
        xhr.open('GET', url)
        xhr.send()
      })
    }

urls 数组中存放了 10 个接口地址。同时还定义了一个 loadDate 函数,这个函数接受一个 url 参数,返回一个 Promise 对象,该 Promise 在接口调用成功时返回 resolve,失败时返回 reject

  • 要求:任意时刻,同时下载的链接数量不可以超过 3 个。
  • 试写出一段代码实现这个需求,要求尽可能快速地将所有接口中的数据得到。

解题思路

按照题意我们可以这样做,首先并发请求 3 个 url 中的数据,当其中一条 url 请求得到数据后,立即发起对一条新 url 上数据的请求,我们要始终让并发数保持在 3 个,直到所有需要加载数据的 url 全部都完成请求并得到数据。

用 Promise 实现的思路就是,首先并发请求3个 url ,得到 3 个 Promise ,然后组成一个叫 promises 的数组。再不断的调用 Promise.race 来返回最快改变状态的 Promise ,然后从数组promises中删掉这个 Promise 对象,再加入一个新的 Promise,直到所有的 url 被取完,最后再使用 Promise.all 来处理一遍数组promises中没有改变状态的 Promise

参考答案

    var urls = [
      'http://jsonplaceholder.typicode.com/posts/1',
      'http://jsonplaceholder.typicode.com/posts/2', 
      'http://jsonplaceholder.typicode.com/posts/3', 
      'http://jsonplaceholder.typicode.com/posts/4',
      'http://jsonplaceholder.typicode.com/posts/5', 
      'http://jsonplaceholder.typicode.com/posts/6', 
      'http://jsonplaceholder.typicode.com/posts/7', 
      'http://jsonplaceholder.typicode.com/posts/8',
      'http://jsonplaceholder.typicode.com/posts/9', 
      'http://jsonplaceholder.typicode.com/posts/10'
    ]
    
    function loadDate (url) {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()
        xhr.onload = function () {
          resolve(xhr.responseText)
        }
        xhr.open('GET', url)
        xhr.send()
      })
    }
    
    function limitLoad(urls, handler, limit) {
        // 对数组进行一个拷贝
        const sequence = [].concat(urls)
        let promises = [];
    
        //实现并发请求达到最大值
        promises = sequence.splice(0, limit).map((url, index) => {
            // 这里返回的 index 是任务在数组 promises 的脚标
            //用于在 Promise.race 后找到完成的任务脚标
            return handler(url).then(() => {
                return index
            }); 
        });
    
        // 利用数组的 reduce 方法来以队列的形式执行
        return sequence.reduce((last, url, currentIndex) => {
            return last.then(() => {
                // 返回最快改变状态的 Promise
                return Promise.race(promises)
            }).catch(err => {
                // 这里的 catch 不仅用来捕获前面 then 方法抛出的错误
                // 更重要的是防止中断整个链式调用
                console.error(err)
            }).then((res) => {
                // 用新的 Promise 替换掉最快改变状态的 Promise
                promises[res] = handler(sequence[currentIndex]).then(
                    () => { return res });
            })
        }, Promise.resolve()).then(() => {
            return Promise.all(promises)
        })
        
    }
    limitLoad(urls, loadDate, 3)
    
    /*
    因为 loadDate 函数也返回一个 Promise
    所以当 所有图片加载完成后可以继续链式调用
    
    limitLoad(urls, loadDate, 3).then(() => {
        console.log('所有url数据请求成功');
    }).catch(err => {
        console.error(err);
    })
    */
Last Updated:
Contributors: leeguooooo