Advanced-Frontend / Daily-Interview-Question

我是依扬(木易杨),公众号「高级前端进阶」作者,每天搞定一道前端大厂面试题,祝大家天天进步,一年后会看到不一样的自己。

Home Page:https://muyiy.cn/question/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

第 153 题:实现一个批量请求函数 multiRequest(urls, maxNum)

yygmind opened this issue · comments

要求如下:

  1. 要求最大并发数 maxNum
  2. 每当有一个请求返回,就留下一个空位,可以增加新的请求
  3. 所有请求完成后,结果按照 urls 里面的顺序依次打出
commented
// 我先来,模拟图片加载过程
function loadImg(url) {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.onload = function() {
        console.log(url, "加载完成");
        resolve(img);
      };
      img.onerror = function() {
        reject(new Error('Error at:' + url));
      };
      img.src = url;
    })
  }
  function multiRequest(urls, maxNum) {
    const firstMaxNum = urls.splice(0, maxNum);
    let promises = firstMaxNum.map((url, index)=>{
      return loadImg(url).then(()=>{
        return index
      })
    })
    return urls.reduce((res, cur)=>{
      return res.then(()=>{
        return Promise.race(promises)
      }).then((idx)=>{
        promises[idx] = loadImg(cur).then(()=>{
          return idx
        })
      })
    }, Promise.resolve()).then(()=>{
      return Promise.all(promises)
    })  
  }
multiRequest(urls, 4).then(()=>{
  console.log('finish')
})
function multiRequest(urls, maxNum) {
  const ret = [];
  let i = 0;
  let resolve;
  const promise = new Promise(r => resolve = r);
  const addTask = () => {
    if (i >= arr.length) {
      return resolve();
    }

    const task = request(urls[i++]).finally(() => {
      addTask();
    });
    ret.push(task);
  }

  while (i < maxNum) {
    addTask();
  }

  return promise.then(() => Promise.all(ret));
}

// 模拟请求
function request(url) {
  return new Promise((r) => {
    const time = Math.random() * 1000;
    setTimeout(() => r(url), time);
  });
}

通过count计数的形式,判断接口是否全部完成

class MultiRequest {
  constructor(props) {
    this.max = props.max || 10; //数量
    this.urls = props.urls || []; //接口
    this.count = 0; //总共完成数量
    this.result = []; //接口请求完成集合
    this.thenCbs = []; //调用then
    this.run(props.urls, 0); //初始化
  }
  add(url) {
    const taskLen = this.urls.filter(v => !isPromise(v));
    if (taskLen > this.max) {
      throw `最多支持${this.max}条`;
    }
    if (Array.isArray(url)) {
      this.run(url, this.urls.length);
      this.urls = [...this.urls, ...url];

      return true;
    }
    return false;
  }
  run(_urls, _urlsLen) {
    console.log(this.urls, _urls, _urlsLen, "------");
    _urls.forEach((v, _i) => {
      const _index = _i + _urlsLen;
      return v
        .then(res => {
          this.count++;
          this.result[_index] = res; 
          if (this.count === this.urls.length && this.thenCbs.length) {
            this.thenCbs.map(_cb => _cb(this.result));
            this.clear();
          }
        })
        .catch(error => {
          this.count++;
          this.result[_index] = error;
          console.log(error);
          if (this.count === this.urls.length && this.thenCbs.length) {
            this.thenCbs.map(_cb => _cb(this.result));
            this.clear();
          }
        });
    });
  }
  clear() {
    this.count = 0;
    this.result = [];
    this.thenCbs = [];
    this.urls = [];
  }
  then(cb) {
    this.thenCbs = [...this.thenCbs, ...[cb]];
    return this;
  }
}


const multiRequest = new MultiRequest({
  max: 10,
  urls: [
    loadImg("https://mirror-gold-cdn.xitu.io/168e096b62f70298fbd"),
    loadImg("https://mirror-gold-cdn.xitu.io/168e096b62f70298fbd"),
    loadImg("https://mirror-gold-cdn.xitu.io/168e096b62f70298fbd")
  ]
});
multiRequest.add([
  loadImg("https://mirror-gold-cdn.xitu.io/168e096b62f70298fbd")
]);
multiRequest.add([
  loadImg("https://mirror-gold-cdn.xitu.io/168e096b62f70298fbd")
]);
console.log(
  multiRequest.add([
    loadImg("https://mirror-gold-cdn.xitu.io/168e096b62f70298fbd")
  ])
);
console.log(
  multiRequest
    .then(_value => {
      console.log(_value, "---1");
    })
    .then(_value => {
      console.log(_value, "---1");
    }),
  "----multiRequest"
);
function isPromise(obj) {
  if (Promise && Promise.resolve) {
    return Promise.resolve(obj) === obj;
  }
  return false;
}

function loadImg(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = function() {
      console.log(url, "加载完成");
      resolve(img);
    };
    img.onerror = function() {
      reject(url);
    };
    img.src = url;
  });
}

借鉴了一些题解的实现,用例跑通了,有问题或者可优化的话请各位大佬指正。

解题的关键是:队列和递归

代码如下

function handleFetchQueue(urls, max, callback) {
  const urlCount = urls.length;
  const requestsQueue = [];
  const results = [];
  let i = 0;
  const handleRequest = url => {
    const req = fetchFunc(url)
      .then(res => {
        results.push(res);
      })
      .catch(e => {
        results.push(e);
      })
      .finally(() => {
        const len = results.length;
        if (len < urlCount) {
          // 完成请求就出队
          requestsQueue.shift();
          handleRequest(urls[++i]);
        } else if (len === urlCount) {
          "function" === typeof callback && callback(results);
        }
      });
    requestsQueue.push(req);
    // 只要满足就继续请求
    if (requestsQueue.length <= max) {
      handleRequest(urls[++i]);
    }
  };
  handleRequest(urls[i]);
}
commented
const req = (url:string):Promise<any> => {
    return new Promise((resolve,reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('get',url,true); //这里第三个参数不能为false,会变成同步
        xhr.onload = () => {
            if(xhr.status === 200){
                resolve(url)
            }else{
                reject(url)
            }
        }
        xhr.send();
    })
}
const multiRequest = (urls:string[],maxNum:number):Promise<any> => {
    let i = 0;
    const ret:any = []; // 完成集合
    const executing:any = [];// 执行集合
    const enqueue = ():Promise<void> => {
        if(urls.length === i){ // 判断是否全部执行完
            return Promise.resolve();
        }
        const p = Promise.resolve().then(() => req(urls[i++]));
        ret.push(p); 
        const e = p.then(() => executing.splice(0,1));// 执行完从executin中剔除一个
        executing.push(e);

        let r = Promise.resolve();
        if(executing.length >= maxNum){// 判断executing中的长度是否大于等于限制数maxNum
            r = Promise.race(executing);
        }
        return r.then(() => enqueue());// 当 r = Promise.race 时会等到其中一个执行完才执行下一步
    }

    return enqueue().then(() => Promise.all(ret)) //全部执行完按顺序返回
}

multiRequest(Array.from({length:10},(u,i) => ('/api/test?' + i)),2)
.then(res => {
    console.log(res)   // ["/api/test?0", "/api/test?1", "/api/test?2", "/api/test?3", "/api/test?4", "/api/test?5", "/api/test?6", "/api/test?7", "/api/test?8", "/api/test?9"]
})

如果网络太好,建议将Network throttling 调成Fast 3G。

class Scheduler {
constructor(count) {
this.count = count;
this.task = [];
this.executing = 0;
}
add(promiseCreator) {
this.task.push(() => promiseCreator());
this.executing < this.count && this.runTask();
}
runTask() {
if (this.task.length === 0) return;
this.executing++
this.task.splice(0, 1)0.then(() => {
this.executing--
this.runTask();
})
}
}

const timeout = (time) => new Promise((resolve, reject) => {
setTimeout(resolve, time);
})

function multiRequest(urls = [], maxNum = 1) {
let scheduler = new Scheduler(maxNum);
const addTask = (time, order) => {
scheduler.add(() => timeout(time).then(() => console.log(order)))
}
for (let i = 0; i < urls.length; i++) {
addTask(1000, urls[i]);
}
}

commented

借鉴了一些题解的实现,用例跑通了,有问题或者可优化的话请各位大佬指正。

解题的关键是:队列和递归

代码如下

function handleFetchQueue(urls, max, callback) {
  const urlCount = urls.length;
  const requestsQueue = [];
  const results = [];
  let i = 0;
  const handleRequest = url => {
    const req = fetchFunc(url)
      .then(res => {
        results.push(res);
      })
      .catch(e => {
        results.push(e);
      })
      .finally(() => {
        const len = results.length;
        if (len < urlCount) {
          // 完成请求就出队
          requestsQueue.shift();
          handleRequest(urls[++i]);
        } else if (len === urlCount) {
          "function" === typeof callback && callback(results);
        }
      });
    requestsQueue.push(req);
    // 只要满足就继续请求
    if (requestsQueue.length <= max) {
      handleRequest(urls[++i]);
    }
  };
}

这个不行吧,请求不是按顺序返回的,切要并发请求吧

// 我先来,模拟图片加载过程
function loadImg(url) {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.onload = function() {
        console.log(url, "加载完成");
        resolve(img);
      };
      img.onerror = function() {
        reject(new Error('Error at:' + url));
      };
      img.src = url;
    })
  }
  function multiRequest(urls, maxNum) {
    const firstMaxNum = urls.splice(0, maxNum);
    let promises = firstMaxNum.map((url, index)=>{
      return loadImg(url).then(()=>{
        return index
      })
    })
    return urls.reduce((res, cur)=>{
      return res.then(()=>{
        return Promise.race(promises)
      }).then((idx)=>{
        promises[idx] = loadImg(cur).then(()=>{
          return idx
        })
      })
    }, Promise.resolve()).then(()=>{
      return Promise.all(promises)
    })  
  }
multiRequest(urls, 4).then(()=>{
  console.log('finish')
})

res是请求链接,res.then鞋油问题吧

说白了,就是实现一个限制并发的 Promise.all

目前设定的是,当执行过程中某个 promise 返回 reject 则停止后续的 promise 执行。

后续可以加上,abort 请求。可以是一个回调,失败时候把 executing 传递过去。回调去执行一些操作。

// Promise.all([promsie1(), promsie2(), promsie3()]);
// PromiseLimit([promsie1, promsie2, promsie3]);
function PromiseLimit(funcArray, limit = 5) {
  let i = 0;
  const result = [];
  const executing = [];
  const queue = function() {
    if (i === funcArray.length) return Promise.all(executing);
    const p = funcArray[i++]();
    result.push(p);
    const e = p.then(() => executing.splice(executing.indexOf(e), 1));
    executing.push(e);
    if (executing.length >= limit) {
      return Promise.race(executing).then(
        () => queue(),
        e => Promise.reject(e)
      );
    }
    return Promise.resolve().then(() => queue());
  };
  return queue().then(() => Promise.all(result));
}
// 测试代码
const result = [];
for (let index = 0; index < 10; index++) {
  result.push(function() {
    return new Promise((resolve, reject) => {
      console.log("开始" + index, new Date().toLocaleString());
      setTimeout(() => {
        resolve(index);
        console.log("结束" + index, new Date().toLocaleString());
      }, parseInt(Math.random() * 10000));
    });
  });
}

PromiseLimit(result).then(data => {
  console.log(data);
});

测试结果,图片不知道为啥不显示

开始0 2020/5/18 上午11:28:47
开始1 2020/5/18 上午11:28:47
开始2 2020/5/18 上午11:28:47
开始3 2020/5/18 上午11:28:47
开始4 2020/5/18 上午11:28:47

结束0 2020/5/18 上午11:28:47
开始5 2020/5/18 上午11:28:47

结束5 2020/5/18 上午11:28:51
开始6 2020/5/18 上午11:28:51

结束4 2020/5/18 上午11:28:53
开始7 2020/5/18 上午11:28:53

结束7 2020/5/18 上午11:28:53
开始8 2020/5/18 上午11:28:53

结束1 2020/5/18 上午11:28:54
开始9 2020/5/18 上午11:28:54

结束3 2020/5/18 上午11:28:54
结束2 2020/5/18 上午11:28:55
结束9 2020/5/18 上午11:28:55
结束6 2020/5/18 上午11:28:56
结束8 2020/5/18 上午11:29:01

 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

// 修改测试代码 随机失败或者成功
const result = [];
for (let index = 0; index < 10; index++) {
  result.push(function() {
    return new Promise((resolve, reject) => {
      console.log("开始" + index, new Date().toLocaleString());
      setTimeout(() => {
        if (Math.random() > 0.5) {
          resolve(index);
        } else {
          reject(index);
        }
        console.log("结束" + index, new Date().toLocaleString());
      }, parseInt(Math.random() * 1000));
    });
  });
}
PromiseLimit(result).then(
  data => {
    console.log("成功", data);
  },
  data => {
    console.log("失败", data);
  }
);

开始0 2020/5/18 上午11:34:37
开始1 2020/5/18 上午11:34:37
开始2 2020/5/18 上午11:34:37
开始3 2020/5/18 上午11:34:37
开始4 2020/5/18 上午11:34:37
结束4 2020/5/18 上午11:34:37
失败 4
结束0 2020/5/18 上午11:34:37
结束3 2020/5/18 上午11:34:37
结束1 2020/5/18 上午11:34:37
结束2 2020/5/18 上午11:34:37

// 这题如果maxNum 为无限大,其实就是在让你实现Promise.all
// 如果是有一个失败就返回 就是Promise.race    
function multiRequest(urls = [], maxNum) {
      let result = new Array(urls.length).fill(false)
      let sum = urls.length; //总数
      let count = 0;             //已完成数
      return new Promise((resolve, reject) => {
        //先请求maxNum个呗    
        while (count < maxNum) {
          next()
        }
        function next() {
          let current = count++
          // 边界
          if (current >= sum) {
            !result.includes(false) && resolve(result)
            return
          }
          let url = urls[current];
          console.log("开始:" + current, new Date().toLocaleString());
          fetch(url).then((res) => {
            console.log("结束:" + current, new Date().toLocaleString());
            result[current] = res
            //还有未完成,递归;
            if (current < sum) {
              next()
            }
          }).catch((err) => {
            console.log("结束:" + current, new Date().toLocaleString());
            result[current] = err
            if (current < sum) {
              next()
            }
          })
        }
      })
 }
    let url2 = `https://api.github.com/search/users?q=d`;
    let arr = new Array(100).fill(url2)
    multiRequest(arr, 10).then((res) => {
      console.log(res)
    })

@SupermanWY
借鉴了你的写法,但是有个地方有bug,while循环那里,如果输入的urls的长度小于maxNun,就会一直执行了,所以需要限制一下

const fetch = (url) => {
  return new Promise((rs) => {
    const s = Math.random() * 1000
    setTimeout(() => {
      rs(url)
    }, s)
  })
}

const multiRequest = (urls, maxNum) => {
  let allPromise = []
  let len = urls.length
  let resolve
  let indirectPromise = new Promise((rs) => {
    resolve = rs
  })
  let i = 0

  let addFetch = () => {
    if (i >= len) {
      return resolve()
    }
    let tempP = fetch(urls[i++]).finally(() => {
      addFetch()
    })
    allPromise.push(tempP)
  }
  // 这里需要限定一下
  while (i < maxNum && i < len ) {
    addFetch()
  }

  return indirectPromise.then(() => {
    return Promise.all(allPromise)
  })
}

multiRequest([
  '/a',
  '/b',
  '/c',
  '/d',
  '/e',
  '/f',
], 40).then((res) => {
  console.log(res)
})
function multiRequest(urls, maxNum) {
  let i = 0
  let res = []
  return new Promise(r => {
    for(; i < maxNum; i++) {
      addTask()
    }
    function addTask() {
      res[i] = fetch(urls[i])
      res[i].then(res => { i >= urls.length && r();i < urls.length && addTask(); i++;  })
    }
  }).then((_) => {
    Promise.all(res).then(ans => {
      console.log(ans)
    })
  })
}
function fetch(url) {
  return new Promise((resolve) => {
    let start = new Date()
    setTimeout(() => {
      resolve(`start: ${start};end: ${new Date()}`)
    }, 10000 * Math.random());
  })
}
multiRequest([1,2,3,4,5,6,7,8,9], 3)

我第一感觉是:他让我写个promise.all

const apiPath = 'http://localhost:3000/';
let urlList = [${apiPath}test1, ${apiPath}test2, ${apiPath}test3, ${apiPath}test4,${apiPath}test5];
function request(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open('get', url, true); //这里第三个参数不能为false,会变成同步
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200 || xhr.status == 304) {
resolve(xhr.responseText)
}
}
}
xhr.send()
})
}

后台koa启个node服务 大概是
router.get('/test1', async (ctx) => {
let res = await delay(1000)
ctx.type = 'application/json';
ctx.body = {
status: 200,
msg: 'test1 OK'
}

});
router.get('/test2', async (ctx) => {
let res = await delay(2000)
ctx.type = 'application/json';
ctx.body = {
status: 200,
msg: 'test2 OK'
}
});
router.get('/test3', async (ctx) => {
let res = await delay(3000)
ctx.type = 'application/json';
ctx.body = {
status: 200,
msg: 'test3 OK'
}
});
router.get('/test4', async (ctx) => {
let res = await delay(4000)
ctx.type = 'application/json';
ctx.body = {
status: 200,
msg: 'test4 OK'
}
});
router.get('/test5', async (ctx) => {
let res = await delay(5000)
ctx.type = 'application/json';
ctx.body = {
status: 200,
msg: 'test5 OK'
}
});

function multiRequest(urls, maxNum = 8) {
let currentUrls;
let curIdx = 0
let currentResList = []
function next() {
currentUrls = urls.slice(curIdx,curIdx+maxNum)
return new Promise((resolve) => {
for (let item of currentUrls) {
request(item).then(res => {
currentResList.push(res)
// console.error(curIdx,currentResList,currentUrls)
if ( (currentResList.length)-curIdx == currentUrls.length ) {
curIdx = curIdx + currentUrls.length
if(curIdx < urls.length ){
next().then(resolve)
}else{
resolve(currentResList)
}
}
})
}
})
}
return new Promise(resolve=>{
next().then(res => {
resolve(res)
})
})
}
multiRequest(urlList, 2).then(res=>{
console.error(res,'rsss')
})
大概这样

function request (url) {
    return new Promise (resolve => {
        const time = Math.random() * 1000
        setTimeout(() => resolve(`${url} + ${Date.now()}`), time)
    })
}

function maxNum (urls, maxNum) {
    this.maxNum = maxNum
    this.urls = urls
    this.requestArr = []
    this.requestQueue = []
    this.result = []
    this.renderQueue = function () {
        this.requestQueue = []
        this.requestArr = []
        this.requestQueue = this.urls.splice(0, this.maxNum)
        this.renderRequest()
    }
    this.renderRequest = function () {
        this.requestQueue.forEach(item => {
            this.requestArr.push(request(item))
        })
    
        Promise.all(this.requestArr).then(res => {
            this.result = this.result.concat(res)
            if (this.urls.length) {
                this.renderQueue()
            } else {
                this.printRes()
            }
        })
    }

    this.renderQueue()
    
    this.printRes = function () {
        console.log(this.result, 'result')
        return this.result
    }
}

let urls = ['www.test.com/1', 'www.test.com/2', 'www.test.com/3']
maxNum(urls, 2)
function multiRequest(urls, maxNum) {
    return new Promise((resolve, reject) => {
        const result = Array(urls.length);
        let resNum = 0;
        let promiseIndex = 0;

        while (promiseIndex < maxNum) {
            genNextFetch();
        }

        function genNextFetch() {
            if (promiseIndex >= urls.length) return;
            const curIndex = promiseIndex++;
            fetch(urls[curIndex])
                .then((res) => {
                    result[curIndex] = res;
                    resNum += 1;
                    if (resNum === urls.length) {
                        resolve(result);
                    } else {
                        genNextFetch();
                    }
                })
                .catch((err) => {
                    reject(err);
                });
        }
    });
}
/**
* @param {Array<string>} urls
* @param {number} maxNum
* @return {Promise<Array<any>>}
*/
function multiRequest (urls, maxNum) {
  const tasks = urls.map(url => request(url));
  const tasksLen = tasks.length;
  const result = [];

  return new Promise((resolve, reject) => {
    const runTask = (index) => {
      if (result.filter(item => item).length === tasksLen) {
        return resolve(result);
      };
  
      const min = Math.min(tasks.length, maxNum);
      for (let i = 0; i < min; i++) {
        const task = tasks.shift();
        task.then(res => {
          result[index + i] = res;
        }).catch(reject).finally(() => {
          runTask(min);
        });
      }
    }
    runTask(0);
  });
}

const urls = ['request 1', 'request 2', 'request 3', 'request 4', 'request 5'];
multiRequest(urls, 4).then(res => {
  console.log('result: ', res);
  console.log('finish...');
});

// 模拟请求
function request(url) {
  return new Promise((resolve, reject) => {
    const time = Math.random() * 5000;
    setTimeout(() => resolve(url), time);
  });
}
function multiRequest(urls, maxNum) {
        return new Promise((res, rej) => {
          const tasksLength = urls.length
          const result = []
          let i = 0
          let done = 0
          function runTask() {
            if(i >= tasksLength) return

            const curIndex = i
            const url = urls[curIndex]
            fetch(url).then(res => res.json()).then((value) => {
              result[curIndex] = value
              done++
              if(done === tasksLength) {
                res(result)
                return
              }
              runTask()
            }).catch(err => {
              rej(err)
            })
            i++
          }

          while(maxNum !== 0) {
            runTask()
            maxNum--
          }
        })
      }
function request (url) {
    return new Promise (resolve => {
        const time = Math.random() * 1000
        setTimeout(() => resolve(`${url} + ${Date.now()}`), time)
    })
}

function maxNum (urls, maxNum) {
    this.maxNum = maxNum
    this.urls = urls
    this.requestArr = []
    this.requestQueue = []
    this.result = []
    this.renderQueue = function () {
        this.requestQueue = []
        this.requestArr = []
        this.requestQueue = this.urls.splice(0, this.maxNum)
        this.renderRequest()
    }
    this.renderRequest = function () {
        this.requestQueue.forEach(item => {
            this.requestArr.push(request(item))
        })
    
        Promise.all(this.requestArr).then(res => {
            this.result = this.result.concat(res)
            if (this.urls.length) {
                this.renderQueue()
            } else {
                this.printRes()
            }
        })
    }

    this.renderQueue()
    
    this.printRes = function () {
        console.log(this.result, 'result')
        return this.result
    }
}

let urls = ['www.test.com/1', 'www.test.com/2', 'www.test.com/3']
maxNum(urls, 2)

@sheepshine 题目中的条件2、每当有一个请求返回,就留下一个空位,可以增加新的请求, 直接用promise.all应该做不到吧?

    async function multiRequest(urls, maxNum) {
      let data = urls.map((url, index) => ({ index, url }))
      let result = []
      let promises = Array.from({ length: Math.min(maxNum,data.length) }, () => getChain(data, result))
      await Promise.all(promises)
      return result
    }

    async function getChain(data, res = []) {
      while (data.length) {
        let one = data.pop()
        try {
          let urlRes = await fetch(one.url)
          res[one.index] = urlRes
        }
        catch (e) {
          res[one.index] = e
        }
      }
    }
    // 调用
    let arr = Array.from({ length: 100 }, () => 'https://www.baidu.com')
    let finalRes = await multiRequest(arr, 5)
    console.log('done', finalRes)
function multiRequest(urls, maxNum) {
  const ret = [];
  let i = 0;
  let resolve;
  const promise = new Promise(r => resolve = r);
  const addTask = () => {
    if (i >= arr.length) {
      return resolve();
    }

    const task = request(urls[i++]).finally(() => {
      addTask();
    });
    ret.push(task);
  }

  while (i < maxNum) {
    addTask();
  }

  return promise.then(() => Promise.all(ret));
}

// 模拟请求
function request(url) {
  return new Promise((r) => {
    const time = Math.random() * 1000;
    setTimeout(() => r(url), time);
  });
}

const promise = new Promise(r => resolve = r);
这行看不懂。。。。好尴尬

commented

递归调用来实现,这个想法是,最初您发送的请求数量上限为允许的最大值,并且这些请求中的每一个都应该在完成时继续递归发送,通过传入的索引来确定了urls 里面具体是那个URL,保证最后输出的顺序不会乱,而是依次输出;

function batchFetch(urls, concurrentRequestsLimit) {
  return new Promise(resolve => {
      var documents = [];
      var index = 0;

      function recursiveFetch(num) {
          if (index === urls.length) {
              return;
          }
          request(urls[index++]).then(r => {
              documents[num] = r;
              if (documents.length === urls.length) {
                  resolve(documents);
              } else {
                  recursiveFetch(index);
              }
          });
      }

      for (let i = 0; i < concurrentRequestsLimit; i++) {
          recursiveFetch(i);
      }
  });
}

var sources = [
  'http://www.example_1.com/',
  'http://www.example_2.com/',
  'http://www.example_3.com/',
  'http://www.example_4.com/',
  'http://www.example_5.com/',
  'http://www.example_6.com/'
];
// 测试用例
function request(url) {
  return new Promise(r => {
    const time = Math.random() * 1000;
    setTimeout(() => r(url), time);
  });
}

batchFetch(sources, 3).then(documents => {
 console.log(documents);
});
function multiRequest(urls = [], maxNum) {
    // 请求总数量
    const sum = urls.length;
    // 根据请求数量创建一个数组来保存请求的结果
    const result = new Array(sum).fill(false);
    // 当前完成的数量
    let count = 0;

    return new Promise((resolve, reject) => {
        // 请求maxNum个
        while (count < maxNum) {
            next();
        }
        function next() {
            let current = count++;
            // 处理边界条件
            if (current >= sum) {
                // 请求全部完成就将promise置为成功状态, 然后将result作为promise值返回
                !result.includes(false) && resolve(result);
                return;
            }
            const url = urls[current];
            console.log(`开始 ${current}`, new Date().toLocaleString());
            fetch(url).then(res => {
                // 保存请求结果
                result[current] = res;
                console.log(`完成 ${current}`, new Date().toLocaleString());
                // 请求没有全部完成, 就递归
                if (current < sum) {
                    next();
                }
            }).catch(err => {
                console.log(`结束 ${current}`, new Date().toLocaleString());
                result[current] = err;
                // 请求没有全部完成, 就递归
                if (current < sum) {
                    next();
                }
            });
        }
    });
}

const url = `https://www.baidu.com/s?wd=javascript`;
const urls = new Array(100).fill(url);

(async () => {
    const res = await multiRequest(urls, 10);
    console.log(res);
})();
commented

看了下题目,不知道我这种实现对不对?没有验证过:

// 以 axios.post 为例
const multiRequest = (urls, maxNum) => {
  return new Promise(resolve => {
    let next = maxNum // 下一个准备发送的请求下标
    let results = []

    const p = (index) => {
      axios.post(urls[index]).then(res => {
        results[index] = res
        const i = next
        ++next
        if (i < urls.length) {
          p(next)
        }
        if (results.length === urls.length) {
          resolve(results)
        }
      })
    }

    for (let i = 0; i < urls.slice(0, maxNum); i++) {
      p(i)
    }
  })
  
}

实现

/**
 * 
 * @param {Array<() => any>} funcArray
 * @param {number} limit 最大请求次数
 */
export function promiseLimit(funcArray, limit = 5) {
  return new Promise((resolve) => {
    let i = funcArray.length >= limit ? limit : funcArray.length
    const result = []
    function execFunc(func) {
      if (i > funcArray.length) return resolve(result)
      let p = func()
      p = p instanceof Promise ? p : Promise.resolve(p)
      p.then(
        () => execFunc(funcArray[i++]),
        () => execFunc(funcArray[i++])
      )
      result.push(p)
    }
    result.concat(funcArray.slice(0, i).map(func => execFunc(func)))
  })
}

测试

function testPromiseLimit(base) {
  let a = []
  for (let i = 0; i < base; i++) {
    a.push(() => {
      console.log(`开始 ${i}`)
      return new Promise((resolve, reject) => {
        Math.random() > 0.5 ? setTimeout(() => resolve('success'), i * 500) : setTimeout(() => reject('fail'), i * 500)
      })
    })
  }
  return a
}
promiseLimit(testPromiseLimit(3))
promiseLimit(testPromiseLimit(5))
promiseLimit(testPromiseLimit(20))

感觉就是一个promiseAll的变种

var Fetch = url => {
	return new Promise(resolve => {
		setTimeout(() => {
			resolve(url)
		}, 1000)
	})
}
var multiRequest = (urls, maxNum) => {
	return new Promise((resolve, reject) => {
		let resolvedNums = 0
		let totalRequests = urls.length
		let resolvedValues = new Array(totalRequests)
		let urlIndex = maxNum - 1

		const rfn = index => {
			Promise.resolve(Fetch(urls[index])).then(value => {
				resolvedNums++
				urlIndex++
				resolvedValues[index] = value
				if(resolvedNums === totalRequests) {
					return resolve(resolvedValues)
				} else {
					if(urlIndex < totalRequests) {
						rfn(urlIndex)
					}
				}
			})
		}

		for(let i = 0; i < maxNum; i++) {
			rfn(i)
		}
	})
}

multiRequest(['api/1','api/2','api/3','api/4','api/5'], 2).then(res =>{
	console.log(res)
})

大佬们,我这样写可行?

function multiRequest(urls, maxNum) {
    return new Promise((resolve, reject) => {
        var result = new Map()
        for (var i = 0; i < maxNum; i++) {
            fn()
        }

        function fn() {
            if (urls.length) {
                var url = urls.shift()
                result.set(url, null)
                return fetch(url).then(res => {
                    result.set(url, res)
                    fn()
                }).catch(err => {
                    result.set(url, err)
                    fn()
                })
            } else {

                resolve(result)
            }
        }
    })
}

var urls = [
    'https://avatars0.githubusercontent.com/u/26534692',
    'https://avatars3.githubusercontent.com/u/19216813',
    'https://avatars3.githubusercontent.com/u/4506712',
    'https://avatars1.githubusercontent.com/u/6983391',
    'https://avatars3.githubusercontent.com/u/22212189',
    'https://avatars0.githubusercontent.com/u/22443771',
]
multiRequest(urls, 2).then(res => {
    console.log(res)
})
commented
function multiRequest(urls, maxNum) {
    let len = urls.length, max = Math.min(len, maxNum), res = []
    let start = 0, end = 0
    return new Promise(resolve => {
        for (let i = 0; i < max; i++) {
            next(resolve, i)
        }
    })
    function next(resolve, index) {
        start++
        if (start === len) {
            return
        }
        fetch(urls[index])
            .then(r => { res[index] = r })
            .catch(e => { res[index] = e })
            .finally(() => {
                end++
                if (end === len) {
                    resolve(res)
                } else {
                    next(resolve, start)
                }
            })
    }
}
const getRes = (url: string) => {
  return new Promise(resolve => {
    const random = Math.random() * 100;
    setTimeout(() => {
      resolve({url, random});
    }, random);
  });
};

const multiRequest = (urls: string[], maxNum: number): Promise<Array<string>> => {
  // 执行队列
  const ret: Promise<any>[] = [];
  let i = 0,
    resolve: () => void;

  const promise = new Promise(r => (resolve = r));

  const addTask = () => {
    if (i >= urls.length) {
      return resolve();
    }

    const task = getRes(urls[i++])
      .then(res => {
        console.log(res);
        return res;
      })
      .finally(() => {
        addTask();
      });

    ret.push(task);
  };

  while (i < maxNum) {
    addTask();
  }

  return promise.then(() => Promise.all(ret));
};

multiRequest(['1', '2', '3', '4', '5', '6', '7', '8', '9'], 5);

// multiRequest(['123'], 3);

export default multiRequest;
commented
  const multiRequest = (urls, maxNum) => {
    return new Promise((re, rj) => {
      let reqs = urls.slice(0, maxNum)
      let res = []
    
      const singleReq = (req, index) => {
        if(res.length == urls.length) {
          return re(res)
        }
        Promise.resolve(req).then((v)=>{
          res[index] = v
        })
      }
      for(let i=0; i<reqs.length; i++){
        Promise.resolve(reqs[i]).then((v)=>{
          res[i] = v
          singleReq(urls[maxNum], maxNum++)
        })
      }
    })
  }

  function reqCeate(url) {
    return new Promise((r) => {
      const time = Math.random() * 1000;
      setTimeout(() => r(url), time);
    });
  }

  let reqs = [1,3,4,6,7,8,9]
  reqs = reqs.map((v) => reqCeate(v))
  multiRequest(reqs, 4).then(console.log)




var dataLists = [1,2,3,4,5,6,7,8,9,11,100,123];

const spreadLimit = (tasks, limitLen) => {
  const mockReq = (task) => {
    return new Promise((resolve, rj) => {
       setTimeout(() => {
          resolve()
          console.log(task)
        }, Math.random() * 2000)
    })
  };

  const next = (task) => {
    return mockReq(task).then(() => {
      return (tasks.length == 0) ? 'done' : next(tasks.shift())
    })
  }

  const arr = tasks.splice(0, limitLen).map((task) => next(task));
  Promise.all(arr).then(() => console.log('done'));
}

spreadLimit(dataLists, 3)
commented
class MutiRequest {
    constructor(urls, max) {
        this.urls = urls
        this.max = max
        this.pending = []
        this.excuting = []
        this.result = []
        this.init(urls)
    }

    init(urls) {
        for (let i = 0; i < urls.length; i++) {
            this.add(fetchApi(urls[i]), i)
        }
    }

    add(promiseFn, index) {
        if (this.excuting.length === this.max) {
            return new Promise((resolve) => {
                this.pending.push(() => {
                    this.excute(promiseFn, resolve)
                })
            }).then((data) => {
                console.log(index, data)
                this.result[index] = data
                this.printOut()
            })
        } else {
            return new Promise((resolve) => {
                this.excute(promiseFn, resolve)
            }).then((data) => {
                console.log(index, data)
                this.result[index] = data
                this.printOut()
            })
        }
    }

    printOut () {
        if (this.result.length === this.urls.length) {
            console.log(this.result)
        }
    }

    excute(promiseFn, resolve) {
        this.excuting.push(promiseFn)
        promiseFn.then((data) => {
            resolve(data)
            this.excuting = this.excuting.filter((item) => {
                return item !== promiseFn
            })
            if (this.pending.length) {
                this.pending.shift()()
            }
        })
    }
}

// 模拟请求
const fetchApi = (url) => {
    return new Promise((resolve, reject) => {
        let random = Math.random() * 10
        setTimeout(() => {
            resolve('res:' + url)   
        }, 1000 * random)
    });
}

// 测试
let mutiTest = new MutiRequest([
    'https://api.xx.com/01',
    'https://api.xx.com/02',
    'https://api.xx.com/03',
    'https://api.xx.com/04',
    'https://api.xx.com/05',
    'https://api.xx.com/06',
    'https://api.xx.com/07',
    'https://api.xx.com/08',
    'https://api.xx.com/09',
    'https://api.xx.com/10',
    'https://api.xx.com/11',
    'https://api.xx.com/12',
    'https://api.xx.com/13',
    'https://api.xx.com/14',
    'https://api.xx.com/15',
], 2)
commented

用generator写一个

var multiRequest = (urls, max, callback) => {
    return new Promise((resolve) => {
        let limit = max
        let currentTaskNums = 0
        let finishedNum = 0
        let index = 0
        let res = []
        function* genFn(urls) {
            for (let i = 0; i < urls.length; i++) {
                yield [fetch(urls[i]), i]
            }
        }
        var gen = genFn(urls)
        function step() {
            currentTaskNums++
            index++
            let { value } = gen.next()
            value[0].then((data) => {
                res[value[1]] = data // 这个要处理下结果
                currentTaskNums--
                finishedNum++
                if (index < urls.length) {
                    step()
                } else if (index ===  urls.length && finishedNum === urls.length) {
                    callback()
                    resolve(res)
                }
            })
            if (currentTaskNums < limit) {
                step()
            }   
        }
        step()
    })
}

multiRequest 实现

  • ajax: 模拟接口请求
  • push:压入对列
  • commonHandle: 统一处理,出队和更新数据
  • next:核心迭代器
  • start:启动函数
function multiRequest(urls, maxNum) {
    const now = Date.now();
    // 结果队列
    const result = [];
    // 请求队列
    const queue = [];
    // 进行中队列
    const pending = [];

    // 模拟请求
    const ajax = function (url) {
        const random = Math.random() * 10;
        const maxTimeout = 500 * 5;
        const timeout = Math.floor(random * 1000);
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if (timeout > maxTimeout) {
                    console.log(`success: [url: ${url}; timeout: ${timeout}, '耗时:' ${Date.now() - now}]`);
                    reject(`${url} fail: ${timeout}`);
                } else {
                    console.log(`fail: [url: ${url}; timeout: ${timeout}, '耗时:' ${Date.now() - now}]`);
                    resolve(`${url} success: ${timeout}`);
                }
            }, timeout)
        });
    }

    // 压入请求队列
    const push = function (item) {
        result.push(item);
        queue.push(item.url);
    }

    // 统一处理
    const commonHandle = (url, res) => {
        // 更新result
        result.forEach((item) => {
            if (item.url === url) {
                item.result = res;
            }
        })
        const index = pending.indexOf(url);
        pending.splice(index, 1);

    }



    return new Promise((resolve) => {
        // 下一个
        const next = function () {
            if (result.filter(item => !item.result).length === 0) {
                resolve(result);
                return;
            }

            // 请求队列为空
            if (queue.length === 0) {
                return;
            };

            // 进行中队列小于maxNum
            if (pending.length < maxNum) {
                // 请求对列出
                const url = queue.shift();
                // 进行中对列进入
                pending.push(url);

                ajax(url)
                    .then((res) => {
                        commonHandle(url, res);
                        next()
                    }).catch((err) => {
                        commonHandle(url, err);
                        next()
                    })

            }
        }

        // 请求
        const request = function (item) {
            push(item);
            next();
        }

        // 启动
        const start = function () {
            for (let i = 0; i < urls.length; i++) {
                const url = urls[i];
                const item = {
                    url,
                    result: '',
                };
                request(item);
            }
        }
        start();

    })
}

测试代码

function main() {
    let urls = [];
    for (let i = 0; i < 15; i++) {
        urls.push(`url${i}`);
    }
    multiRequest(urls, 10).then((res) => {
        console.log(res);
    });
}
main();

面试被问到过,写一写:

function fetchLimit(urls, max) {
        let num = 0;
        let len = urls.length;
        let _res = [];

        function req(url) {
            fetch(url)
                .then(res => _res[num] = res)
                .catch(err => _res[num] = err)
                .finally(() => {
                    if (num >= len) return _res;

                    req(urls[num])
                });
            ++num;
        }


        for (let i = 0; i < max; i++) {
            req(urls[i])
        }
    }
commented
function multiRequest(urls, nums) {
    let result = {}

    function fetchData(url, t) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('result: ' + url)
            }, t)
        })
    }

    function request() {
        return new Promise((resolve, reject) => {
            for (let index = 0; index < urls.length; index++) {
                const element = urls[index];
                if (!result[element]) {
                    fetchData(element, 1000 * (index + 2)).then((r) => {
                        result[element] = r
                        if (Object.keys(result).length === urls.length) {
                            resolve(result)
                        }
                    })
                }
            }
        })
    }

    function add(url) {
        if (urls.length < nums) {
            urls.push(url)
            return request()
        }
    }

    return{
        add,
        request
    }
}

function test() {
    let urls = ['a', 'b', 'c']
    const nums = 5
    const {add, request} = multiRequest(urls, nums)

    function print(result) {
        for (let index = 0; index < urls.length; index++) {
            const element = urls[index];
            console.log('index', index, 'url', element, 'result', result[element])            
        } 
    }

    request().then(print)
    setTimeout(() => {
        add('d').then(print)
    }, 4000)
}

test()
function multiRequest(urls, maxNum){
    let res = [], idx = 0, count = 0, len = urls.length;
    return new Promise((resolve, reject)=>{
        let next = function(){
            maxNum--;            
            Promise.resolve(urls[idx++]).then(val=> {
                res[count++] = {status: 'fulfilled', value: val};                
            }, err=> {
                res[count++] = {status: 'rejected', value: err};;              
            }).finally(()=> {
                if(idx<len){
                    maxNum++;
                    next();
                }else{
                    resolve(res)
                }
            })    
        }        
        while(idx < len && maxNum > 0){
            next()   
        }
    })
}
commented
class Scheduler {
    constructor(maxNum) {
      //等待执行的任务队列
      this.taskList = []
      //当前任务数
      this.count = 0
      //最大任务数
      this.maxNum = maxNum
    }
      
    async add(promiseCreator) {
       //当当前任务数超出最大任务数就将其加入等待执行的任务队列
      if (this.count >= this.maxNum) {
        await new Promise(resolve => {
          this.taskList.push(resolve)
        })
      }
      this.count++
      const result = await promiseCreator()
      this.count--
      //当其它任务执行完任务队列中还有任务没执行就将其出队并执行
      if (this.taskList.length > 0) {
        this.taskList.shift()()
      }
      return result;
    }
  }
// 模拟请求
function request(url) {
  return new Promise((r) => {
    const time = Math.random() * 1000;
    setTimeout(() => r(url), time);
  });
}

function multiRequest(urls, maxNum){
  const requests = [];
  const scheduler = new Scheduler(maxNum);
  for(let i = 0, len = urls.length; i < len; i++) {
      requests.push(scheduler.add(() => request(urls[i])))
  }
  Promise.all(requests).then((res) => res.forEach((r) => console.log(r)))
}
commented
function multiRequest(urls, maxNum) {
  const waitUrls = [] // 等待执行的url队列

  function fetchUrl(url) {
    delay(url).then((res) => {
      console.log('res: ', res)
      if (waitUrls.length) {
        fetchUrl(waitUrls[0])
        waitUrls.shift()
      }
    })
  }
  urls.forEach((url, index) => {
    if (index < maxNum) {
      fetchUrl(url)
    } else {
      waitUrls.push(urls[index])
    }
  })
}

/**
 * test
 */
function delay(result) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(result)
    }, 2000)
  })
}
multiRequest(['xxx.json', 'yyy.json', 'zzz.json', 'qqq.json', 'www.json'], 2)
function request(url) {
    const timeOut = Math.random() * 10;
    const result = url +': ' + timeOut + 's';
    return new Promise((res, rej) => {
        try {
            setTimeout(() => {
                // console.log(url, result);
                res(result);
            }, timeOut * 1000);
        } catch (error) {

            console.error(url, error);
            rej(error);
        }
    });
}
function multiRequest(urls, maxNum = 3) {
    let requestedIndex = 0;
    let currentReqestting = 0;
    const results = {};

    function pushRequest(url, callback) {
        if (requestedIndex >= urls.length || currentReqestting >= maxNum) {
            return;
        }
        request(url)
        .then((res) => {
            results[url] = res;

            currentReqestting--;
            if (Object.keys(results).length >= urls.length) {
                callback();
            }
            pushRequest(urls[requestedIndex], callback);
        })
        .catch(e => {
            results[url] = e;

            currentReqestting--;
            if (Object.keys(results).length >= urls.length) {
                callback();
            }
            pushRequest(urls[requestedIndex], callback);
        });
        currentReqestting++;
        requestedIndex++;
        pushRequest(urls[requestedIndex], callback);
    }

    return new Promise((res, rej) => {
        try {
            if(requestedIndex < urls.length && currentReqestting < maxNum){
                pushRequest(urls[requestedIndex],() => {
                    res({
                        urls,
                        results
                    });
                });
            }
        } catch (error) {
            rej({
                urls,
                error
            });
        }
    });
}

multiRequest(['/sale_center/sale_order/', '/sale_center/klicen_order/', '/sale_center/terminal_order/', '/sale_center/xyt_order/', '/sale_center/patch_order/'])
.then(({ urls, results }) => {
    console.log('------------------------------');
    console.log(results);
    urls.forEach(url => {
        console.log(results[url]);
    });
});
    function multiRequest(urls, maxNum, callback) {
		var arr = [];
		var count = 0;
		for (let i = 0; i < maxNum; i++) {
			ajax(i)
		}
		function ajax(i) {
			if (urls[i] !== undefined && urls[i] !== null) {
				setTimeout(function () {
					count++;
					success(urls[i], i)
				}, Math.random() * 10000)
			}
		}
		function success(res, i) {
			arr[i] = res;
			if (count === urls.length) {
				callback(arr);
			} else {
				let index = maxNum + count - 1
				ajax(index)
			}
		}
	}
function multiRequests(urls, maxNums) {
	let total = urls.length;
	let results = [];
	return new Promise((resolve, reject) => {
		let currIndex = 0; // 当前待执行的下标
		let restNum = total; // 剩余未执行的数量

		// 添加初始值
		for (let i = 0; i < maxNums; i++) {
			if (i < total) {
				addRequest(request(urls[i]), i);
			}
		}

		function addRequest(req, index) {
			currIndex++;
			req.then((res) => {
					results[index] = res;
				})
				.finally(() => {
					restNum--;
					if (currIndex < total) {
						let req = request(urls[currIndex]);
						addRequest(req, currIndex);
					} else if (restNum == 0) {
						resolve(results);
					}
				});
		}
	});
}
commented
// 实现一个批量请求函数 multiRequest(urls, maxNum)
function multiRequest(urls, maxNum) {
    const result = []
    const start = +new Date()
    let resolvedCount = 0
    let pendingCount = 0
    let windowSize = 0

    function generateP(url, pendingCount) {
      return new Promise((res, rej) => {
        setTimeout(() => {
            res({
              data: url,
              index: pendingCount,
            });
        }, url * 1000);
      });
    }

    return new Promise((r) => {
        while (windowSize < maxNum) {
            execP()
        }
        
        function execP() {
            const p = generateP(urls[pendingCount], pendingCount);

            windowSize++;
            pendingCount++;

            p.then(({index, data}) => {
                resolvedCount++;
                windowSize--;
                
                result[index] = {
                    data,
                    time: +new Date() - start,
                };
                
                if (resolvedCount === urls.length) {
                    r(result);
                } else {
                    if (pendingCount < urls.length) {
                        execP();
                    }
                }
            });
        }
    });
}

var start = +new Date()
multiRequest([3, 1, 4, 2], 2).then((data) => {
    console.log(`time passed ${+new Date() - start}`, data);
})
function myFetch(url) {
  const waitTime = Math.random() * 100 + 100;
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(url);
      resolve(url);
    }, waitTime);
  });
}

function multiRequest(urls, maxNum) {
  return new Promise((resolve) => {
    const results = Array.from(urls, () => null);
    let numSuccess = 0;
    let waitIdx = maxNum;
    function triggerNext() {
      if (waitIdx < urls.length) {
        const thisIdx = waitIdx;
        myFetch(urls[thisIdx]).then((r) => {
          results[thisIdx] = r;
          numSuccess++;
          triggerNext();
        });
        waitIdx++;
      }
      if (numSuccess === urls.length) resolve(results);
    }
    for (let i = 0; i < maxNum; i++) {
      myFetch(urls[i]).then((r) => {
        results[i] = r;
        numSuccess++;
        triggerNext();
      });
    }
  });
}

multiRequest([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], 6).then((r) =>
  console.log(r)
);
function myFetch(url) {
  const waitTime = Math.random() * 100 + 100;
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(url);
      resolve(url);
    }, waitTime);
  });
}

function multiRequest(urls, maxNum) {
  return new Promise((resolve) => {
    const results = Array.from(urls, () => null);
    let numSuccess = 0;
    let waitIdx = maxNum;
    function triggerNext() {
      if (waitIdx < urls.length) {
        const thisIdx = waitIdx;
        myFetch(urls[thisIdx]).then((r) => {
          results[thisIdx] = r;
          numSuccess++;
          triggerNext();
        });
        waitIdx++;
      }
      if (numSuccess === urls.length) resolve(results);
    }
    for (let i = 0; i < maxNum; i++) {
      myFetch(urls[i]).then((r) => {
        results[i] = r;
        numSuccess++;
        triggerNext();
      });
    }
  });
}

multiRequest([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], 6).then((r) =>
  console.log(r)
);
 function multiRequest(urls, maxNum) {
      const dl = [];
      const resArr = [];
      let maxIndx = 0;
      function getPromise(i, curIndex) {
        maxIndx++;
        return new Promise((resolve, reject) => {
          console.log("开始" + curIndex, new Date().toLocaleString());
          setTimeout(() => {
            if (Math.random() < 0.5) {
              resolve(curIndex);
            } else {
              reject(new Error(curIndex));
            }
            console.log("结束" + curIndex, new Date().toLocaleString());
          }, 10000 * Math.random());
        }).then(
          (res) => {
            return promiseHandler(res, i, curIndex);
          },
          (err) => {
            return promiseHandler(err, i, curIndex);
          },
        );
      }
      function promiseHandler(res, i, curIndex) {
        resArr[curIndex] = res;
        if (maxIndx < urls.length) {
          return getPromise(i, maxIndx);
        }
        return res;
      }

      for (let i = 0; i < maxNum; i++) {
        dl.push(getPromise(i, i));
      }

      Promise.all(dl).then(() => {
        console.log(resArr);
      });
    }
const req = (url:string):Promise<any> => {
    return new Promise((resolve,reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('get',url,true); //这里第三个参数不能为false,会变成同步
        xhr.onload = () => {
            if(xhr.status === 200){
                resolve(url)
            }else{
                reject(url)
            }
        }
        xhr.send();
    })
}
const multiRequest = (urls:string[],maxNum:number):Promise<any> => {
    let i = 0;
    const ret:any = []; // 完成集合
    const executing:any = [];// 执行集合
    const enqueue = ():Promise<void> => {
        if(urls.length === i){ // 判断是否全部执行完
            return Promise.resolve();
        }
        const p = Promise.resolve().then(() => req(urls[i++]));
        ret.push(p); 
        const e = p.then(() => executing.splice(0,1));// 执行完从executin中剔除一个
        executing.push(e);

        let r = Promise.resolve();
        if(executing.length >= maxNum){// 判断executing中的长度是否大于等于限制数maxNum
            r = Promise.race(executing);
        }
        return r.then(() => enqueue());// 当 r = Promise.race 时会等到其中一个执行完才执行下一步
    }

    return enqueue().then(() => Promise.all(ret)) //全部执行完按顺序返回
}

multiRequest(Array.from({length:10},(u,i) => ('/api/test?' + i)),2)
.then(res => {
    console.log(res)   // ["/api/test?0", "/api/test?1", "/api/test?2", "/api/test?3", "/api/test?4", "/api/test?5", "/api/test?6", "/api/test?7", "/api/test?8", "/api/test?9"]
})

如果网络太好,建议将Network throttling 调成Fast 3G。

比如maxNum设置为2时,第三个请求进来还是先执行了,是不是不对

commented
/**
 *  想法是先构造好请求的一个对象, 包含请求方法, 请求状态, 请求的响应, 通过传入的urls创建出一个请求队列, 第一次就发maxNum个请求,
 *  然后在每个请求完成后, 从请求队列取出状态为0的去请求, 最后所有请求完则返回
 */
function multiRequest(urls, maxNum) {
  return new Promise((resolve, reject) => {
    const queenArr = urls.map(url => {
      const tempObj = {
        request: () => {
          const t = Math.random() * 5000
          tempObj.status = 1
          console.log(queenArr.map(i => i.status));
          setTimeout(() => {
            tempObj.status = 2
            tempObj.response = `请求的url为${url}, 延迟为${t}`
            const req = getRequest()
            if (req) {
              req()
            } else if(queenArr.filter(i => i.status === 1).length === 0) {
              console.log(queenArr.map(i => i.status));
              resolve(queenArr.map(i => i.response))
            }
          }, t)
        },
        status: 0, // 0, 1, 2, 表示请求状态
        response: url
      }
      return tempObj
    })

    // 从请求队列中获取到第一个还未请求的任务
    const getRequest = () => {
      const reqTask = queenArr.filter(i => i.status === 0)[0]
      return reqTask && reqTask['request']
    }
    for (let i = 0; i < maxNum; i++) {
      const req = queenArr[i]
      req && req.request()
    }
  })
}

const urls = ['1', '2', '3', '4', '5']

multiRequest(urls, 2).then((res) => {
  console.log(res)
})
function request (i) {
    return new Promise(rs => setTimeout(rs, i * 1000));
}


async function multiRequest (urls, maxNum) {
    const results = [];

    async function wrapRequest () {
        let index = results.length;
        results[index] = null;
        results[index] = await request(urls[index]);
        if (results.length < urls.length) wrapRequest();
    }

    for (let i = 0; i < maxNum; ++i) {
        wrapRequest();
    }
}

multiRequest([1,4,3,2,1,3], 3);
function multiRequest(urls, maxNum) {
  return new Promise((resolve, reject) => {
    const result = []; // 输出结果
    let count = 0; // 当前有多少个请求
    let finished = 0; // 已经完成的请求数
    const copyUrls = urls.map((url, index) => ({
      url,
      index
    }))
    for (let i = 0; i < Math.min(urls.length, maxNum); i++) {
      queueUrl();
    }
  
    function queueUrl() {
      if (count < maxNum && copyUrls.length) {
        count ++;
        const {url, index} = copyUrls.shift();
        const ajax = sendRequest(url).then(res => {
          result[index] = res;
        }).catch(res => {
          result[index] = res;
        }).finally(() => {
          count --;
          finished ++;
          if (finished >= urls.length) {
            resolve(result)
          } else {
            queueUrl();
          }
        });
      }
    }
  })
}

function sendRequest(url) {
  return new Promise((resolve, reject) => {
    const random = Math.floor(Math.random() * 1000);
    if (random < 200) {
      reject('error...' + url);
    } else {
      setTimeout(() => {
        resolve('resolve...' + url )
      }, random);
    }
  })
}

multiRequest(['11','22','33','44'], 3).then(res => console.log(res))

image

class Scheduler {
    constructor(maxNum) {
      //等待执行的任务队列
      this.taskList = []
      //当前任务数
      this.count = 0
      //最大任务数
      this.maxNum = maxNum
    }
      
    async add(promiseCreator) {
       //当当前任务数超出最大任务数就将其加入等待执行的任务队列
      if (this.count >= this.maxNum) {
        await new Promise(resolve => {
          this.taskList.push(resolve)
        })
      }
      this.count++
      const result = await promiseCreator()
      this.count--
      //当其它任务执行完任务队列中还有任务没执行就将其出队并执行
      if (this.taskList.length > 0) {
        this.taskList.shift()()
      }
      return result;
    }
  }
// 模拟请求
function request(url) {
  return new Promise((r) => {
    const time = Math.random() * 1000;
    setTimeout(() => r(url), time);
  });
}

function multiRequest(urls, maxNum){
  const requests = [];
  const scheduler = new Scheduler(maxNum);
  for(let i = 0, len = urls.length; i < len; i++) {
      requests.push(scheduler.add(() => request(urls[i])))
  }
  Promise.all(requests).then((res) => res.forEach((r) => console.log(r)))
}

this.taskList.push(resolve) 请问这里为什么添加resolve

function ajax(url) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(url)
    }, 10000 * Math.random())
  })
}

function multiRequest(urls, maxNums) {
  return new Promise(resolve => {
    const list = [...urls]
    const result = new Map()
    const pending = new Set()
    const request = (url) => {
      pending.add(url)
      ajax(url).then((data) => onFulfilled(url, data), err => onFulfilled(url, err))
    }
    const onFulfilled = (url, data) => {
      result.set(url, data)
      pending.delete(url)
      if (!pending.size) {
        return resolve([...result.values()])
      } else if (list.length) {
        request(list.shift())
      }
    }
    for (let i = 0; i < urls.length; i++) {
      const url = urls[i]
      result.set(url, null)
      if (i <= maxNums) {
        request(list.shift())
      }
    }
  })
}

const urls = [
  'https://www.hello1',
  'https://www.hello2',
  'https://www.hello3',
  'https://www.hello4',
  'https://www.hello5',
  'https://www.hello6',
  'https://www.hello7',
  'https://www.hello8',
  'https://www.hello9',
  'https://www.hello10',
  'https://www.hello11',
  'https://www.hello12'
]

multiRequest(urls, 3).then(res => {
  console.log('sucssss======')
  console.log(res)
})
function multiRequest(urls, maxNum) {
  return new Promise((resolve) => {
    const roundLength = Math.ceil(urls.length / maxNum)
    const groupTasks = []
    function executTaskGroup (tasks) {
      return Promise.all(tasks.map((url) => {
        return request(url)
      })) 
    }
    for(let i = 0; i < roundLength; i++) {
      const tasks = urls.slice(i * maxNum, i * maxNum + maxNum)
      groupTasks.push(executTaskGroup(tasks))
    }    
    Promise.all(groupTasks).then((res) => {
      resolve(res.flat())
    })
  })
}

function request(url) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(url)
    }, 10000 * Math.random())
  })
}

直接用分頁的概念做,最後用 flat 把結果壓平

const multiRequest = async function (urls, maxNum) {
  const result = [];
  const refObj = { count: 0, index: 0 };
  await new Promise((resolve) => {
    // 同步启动maxNum个任务
    while (refObj.index < maxNum && refObj.index < urls.length) {
      sendRequest(urls, refObj.index, refObj, result, resolve);
      refObj.index++;
    }
  });
  return result;
}

async function sendRequest(urls, index, refObj, result, resolve) {

  // 请求接口,在拿到返回值之后将值写到对应位置上
  const response = await fetch(urls[index])
  const res = await response.json();
  result[index] = res;

  // 此时已经结束了一个请求,判断是不是最后一个url,不是就再开一个请求
  if (refObj.index < urls.length) {
    sendRequest(urls, refObj.index, refObj, result, resolve);
    refObj.index++;
  }

  // 全部完成之后resolve掉
  refObj.count++;
  if (refObj.count === urls.length) {
    resolve();
  }
}
function multiTask(proms, max) {
    let sum = proms.length;
    let count = 0;
    let done = 0;
    const res = [];
    return new Promise((rs, rj) => {
        while (count < max) {
            next();
        }
        function next() {
            const cur = Math.min(count++, sum);
            if (cur >= sum) {
                done === sum && rs(res);
                return;
            }
            proms[cur].then(r => {
                res[cur] = r;
            }).catch(e => {
                res[cur] = e;
            }).finally(() => {
                done++;
                next();
            })
        }
    });
}

function request(url) {
    return new Promise((rs, rj) => {
        setTimeout(() => rs(url), Math.random() * 1e3);
    });
}

const proms = new Array(20).fill(1).map((_, n) => request(n));

multiTask(proms, 10).then(res => {
    console.log(res)
})
commented

控制异步请求的并行个数,内部通过 maxNum 和栈内异步请求是否为空,决定是否拉取最新的请求执行。

class Queue {
  constructor() {
    this.queues = [];
  }

  push(data) {
    return this.queues.push(data);
  }

  shift() {
    return this.queues.shift();
  }

  isEmpty() {
    return this.queues.length === 0;
  }
}

class TaskWrapper {
  constructor(data) {
    const rawData = data;
    this.getRaw = () => rawData;
  }
}

class TaskPool {
  constructor(size) {
    this.size = size;
    this.queue = new Queue();
  }

  /**
   * 添加任务到队列中
   * @param fn
   * @param args
   * @returns Promise
   */
  add(fn, args) {
    return new Promise((resolve) => {
      const task = new TaskWrapper({ resolve, fn, args });
      this.queue.push(task);

      // 根据 size 来初始并发执行指定数量的任务
      // 当 size 为 0 时, 暂停任务的执行, 等待正在执行的异步任务完成后 size 自增
      if (this.size) this.run();
    });
  }

  /**
   * taskFn 执行结果返回一个 Promise 对象实例
   * @param taskFn Promise
   * @param taskFnArgs any
   * @return Promise
   */
  runTask(taskFn, taskFnArgs) {
    const taskFnResult = Promise.resolve(taskFn(taskFnArgs));

    const end = () => {
      this.size++;
      this.pullTask();
    };

    taskFnResult.then(end, end);

    return taskFnResult;
  }

  /**
   * 分批次拉取队列中的任务执行
   * 直到队列为空或者size为0
   */
  pullTask() {
    if (this.queue.isEmpty() || this.size === 0) return;
    this.run();
  }

  /**
   * 为调度运行真正的 task 前做准备
   */
  run() {
    this.size--;
    const { taskResolve, taskFn, taskFnArgs } = this.getRaw();
    taskResolve(this.runTask(taskFn, taskFnArgs));
  }

  /**
   * 获取队头的任务
   * @returns {{taskFn: *, taskResolve: *, taskFnArgs: *}}
   */
  getRaw() {
    const task = this.queue.shift();
    const {
      resolve: taskResolve,
      fn: taskFn,
      args: taskFnArgs,
    } = task.getRaw();
    return {
      taskResolve,
      taskFn,
      taskFnArgs,
    };
  }
}

/**模拟Http请求的函数 */
const request = (url) => {
  return new Promise((resolve) => {
    console.log('发送 Http 请求');
    // 请求 URL
    setTimeout(() => {
      resolve(url);
    }, 2000);
  });
};

/** 实现 */
function multiRequest(urls, maxNum) {
  const taskPool = new TaskPool(maxNum);
  return Promise.all(urls.map((url) => taskPool.add(request, url)));
}

/** 演示 */
multiRequest(['/api/user', '/api/banners', '/api/task'], 1).then((res) => {
  console.log('res:', res);
});

演示三个请求,依次执行的效果,maxNum 为 1

/**
 * 带并发限制的批量请求函数
 * @param {*} urls 
 * @param {*} max 
 * @param {*} callback 
 */
function multiFetch(urls, max, callback) {
  const urlCount = urls.length;
  const urlsWithIndex = urls.map((url, index) => ({ url, index }));
  const result = [];

  // 批量发起请求
  for (let i = 0; i < urlCount && i < max; i++) {
    request(urlsWithIndex.shift());
  }

  function request({ url, index }) {
    fetch(url).then(res => {
      result[index] = res;
    }).catch(err => {
      result[index] = err;
    }).finally(() => {
      // 进行下一个请求
      if (urlsWithIndex.length) request(urlsWithIndex.shift());
      // 全部请求完成
      if (result.length === urlCount) callback(result);
    });
  }
}
// 这题如果maxNum 为无限大,其实就是在让你实现Promise.all
// 如果是有一个失败就返回 就是Promise.race    
function multiRequest(urls = [], maxNum) {
      let result = new Array(urls.length).fill(false)
      let sum = urls.length; //总数
      let count = 0;             //已完成数
      return new Promise((resolve, reject) => {
        //先请求maxNum个呗    
        while (count < maxNum) {
          next()
        }
        function next() {
          let current = count++
          // 边界
          if (current >= sum) {
            !result.includes(false) && resolve(result)
            return
          }
          let url = urls[current];
          console.log("开始:" + current, new Date().toLocaleString());
          fetch(url).then((res) => {
            console.log("结束:" + current, new Date().toLocaleString());
            result[current] = res
            //还有未完成,递归;
            if (current < sum) {
              next()
            }
          }).catch((err) => {
            console.log("结束:" + current, new Date().toLocaleString());
            result[current] = err
            if (current < sum) {
              next()
            }
          })
        }
      })
 }
    let url2 = `https://api.github.com/search/users?q=d`;
    let arr = new Array(100).fill(url2)
    multiRequest(arr, 10).then((res) => {
      console.log(res)
    })

这个let current = count++位置不对,它应该是在执行成功或者失败的回调里。

function req(url) {
  return new Promise((resolve) => {
    console.log('start: ' + url)
    setTimeout(() => {
      console.log('end: ' + url)
      resolve('res' + url)
    }, Math.random() * 1000)
  })
}

const URLS = new Array(25).fill(0).map((_, i) => {
  return i.toString()
})

async function multiRequest(urls, maxNum) {
  const inRequest = [] // 正在请求的任务的promise数组
  const results = [] // 所有任务的promise数组
  const inReqIndxs = [] // 正在请求的任务的index的数组 (仅仅为了log验证请求的并发情况)
  for (let i = 0; i < urls.length; i++) {
    const url = urls[i]
    let p
    // 在下一个请求任务准备进来时,先看下是否正在请求任务数量达到maxNum
    if (inRequest.length === maxNum) {
      // 如果是,则等待任意进行中的任务完成
      await Promise.any(inRequest)
    }
    // 推入下一个任务
    p = req(url)
    inRequest.push(p)
    inReqIndxs.push(i)
    results.push(p)
    console.log(inReqIndxs)

    // 每个任务本身则需要在自身完成时退出任务数组
    p.then((val) => {
      inRequest.splice(inRequest.indexOf(p), 1)
      inReqIndxs.splice(inReqIndxs.indexOf(i), 1)
      results[i] = val
    })
  }
  return Promise.all(results)
}

multiRequest(URLS, 5).then(results => {
  console.log('final results: ' + results)
})
/*

start: 0
[ 0 ]
start: 1
[ 0, 1 ]
start: 2
[ 0, 1, 2 ]
start: 3
[ 0, 1, 2, 3 ]
start: 4
[ 0, 1, 2, 3, 4 ]
end: 4
[ 0, 1, 2, 3 ]
start: 5
[ 0, 1, 2, 3, 5 ]
end: 2
[ 0, 1, 3, 5 ]
...
start: 22
[ 17, 19, 20, 21, 22 ]
end: 20
start: 23
[ 17, 19, 21, 22, 23 ]
end: 17
start: 24
[ 19, 21, 22, 23, 24 ]
end: 19
end: 23
end: 22
end: 21
end: 24
final results: res0,res1,res2,res3,res4,res5,res6,res7,res8,res9,res10,res11,res12,res13,res14,res15,res16,res17,res18,res19,res20,res21,res22,res23,res24
*/
function mutiReqN(n){
    let cur=0
    return function next(qs){
        while(cur < n && qs.length){
            cur++;
            const q = qs.shift();
            q().then((r)=>{
                cur--;
                console.log('请求结束===>', r)
                next(qs)
            })
        }
    }
}

const muti2 = mutiReqN(2);
const np = (n, mark)=> ()=> {
    console.log('发起请求', mark)
    return new Promise(resolve=> setTimeout(()=>resolve(mark), 1000 * n))
};

muti2([
    np(2, 1),
    np(2, 2),
    np(2, 3),
    np(2, 4),
    np(2, 5),
])

最烦这种题目,弯弯绕绕的地方不少,但实际项目中屌用没用。题目其实有个障眼法:那个最大连接数,这个只有在第一次请求时才有效,后面多个请求是分别返回的,不存在并发的情况(JS 是单线程的,及时是多个请求同时返回也需要排队)。

async function multiRequest(urls, maxNum) {
   const urlCount = urls.length;
   const result = {};
   let sendCount = 0;
   let returnCount = 0;

   const doRequest = (url) => {
      return new Promise((resolve) => {
         setTimeout(() => {
            console.log(`request url: ${url}, success`);
            resolve(`data of : ${url}`);
         }, 1000);
      });
   };

   return new Promise((resolve) => {
      const fetchUrlData = (url) => {
         sendCount++;

         return doRequest(url).then((ret) => {
            result[url] = ret;
            returnCount++;
         }).finally(() => {
            if (sendCount < urlCount) {
               fetchUrlData(urls[sendCount]);
            } else {
               if (returnCount === urlCount) {
                  resolve(result);
               }
            }
         });
      }

      Promise.all(urls.slice(0, maxNum).map((url, idx) => {
         return fetchUrlData(url);
      }));
   });
}

var urls = Array.from({ length: 20 }).map((i,idx) => idx + 1);
multiRequest(urls, 3).then((result) => {
   console.log('result: ', result);  
});