Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
yygmind opened this issue May 11, 2020 · 67 comments
Open

Comments

@yygmind
Copy link
Contributor

yygmind commented May 11, 2020

要求如下:

  1. 要求最大并发数 maxNum
  2. 每当有一个请求返回,就留下一个空位,可以增加新的请求
  3. 所有请求完成后,结果按照 urls 里面的顺序依次打出
@leonwens
Copy link

// 我先来,模拟图片加载过程
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')
})

@SupermanWY
Copy link

SupermanWY commented May 11, 2020

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);
  });
}

@NewPrototype
Copy link

NewPrototype commented May 11, 2020

通过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;
  });
}

@ron0115
Copy link

ron0115 commented May 12, 2020

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

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

代码如下

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]);
}

@lhiro
Copy link

lhiro commented May 13, 2020

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。

@myuanyuan
Copy link

myuanyuan commented May 14, 2020

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]);
}
}

@wksmile
Copy link

wksmile commented May 15, 2020

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

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

代码如下

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鞋油问题吧

@ZangYuSong
Copy link

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

@ZangYuSong
Copy link

ZangYuSong commented May 18, 2020

目前设定的是,当执行过程中某个 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));
}

@ZangYuSong
Copy link

ZangYuSong commented May 18, 2020

// 测试代码
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]

@ZangYuSong
Copy link

// 修改测试代码 随机失败或者成功
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

@huangruitian
Copy link

huangruitian commented May 23, 2020

// 这题如果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)
    })

@qianlongo
Copy link

@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)
})

@rottenpen
Copy link

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)

@MaDeLu
Copy link

MaDeLu commented May 26, 2020

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

@mfeishi
Copy link

mfeishi commented May 27, 2020

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'
}
});

@mfeishi
Copy link

mfeishi commented May 27, 2020

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')
})
大概这样

@sheepshine
Copy link

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)

@LamWaiBen
Copy link

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);
                });
        }
    });
}

@jefferyE
Copy link

jefferyE commented Jun 17, 2020

/**
* @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);
  });
}

@NathanHan1
Copy link

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--
          }
        })
      }

@LJJCherry
Copy link

LJJCherry commented Jun 22, 2020

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应该做不到吧?

@leirensheng
Copy link

leirensheng commented Jun 29, 2020

    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)

@Ahuiorg
Copy link

Ahuiorg commented Jul 2, 2020

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);
这行看不懂。。。。好尴尬

@Frank-MJ
Copy link

Frank-MJ commented Jul 9, 2020

递归调用来实现,这个想法是,最初您发送的请求数量上限为允许的最大值,并且这些请求中的每一个都应该在完成时继续递归发送,通过传入的索引来确定了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);
});

@GolderBrother
Copy link

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);
})();

@lizhongzhen11
Copy link

lizhongzhen11 commented Jul 31, 2020

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

// 以 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)
    }
  })
  
}

@wenchao0809
Copy link

实现

/**
 * 
 * @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))

@DaiHangLin
Copy link

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()

@chun1hao
Copy link

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()   
        }
    })
}

@mynzk
Copy link

mynzk commented Dec 30, 2020

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)))
}

@pan-Z2l0aHVi
Copy link

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)

@RyuuPaang
Copy link

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]);
    });
});

@xiaowuge007
Copy link

    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)
			}
		}
	}

@Luz-Liu
Copy link

Luz-Liu commented Feb 20, 2021

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);
					}
				});
		}
	});
}

@negativeentropy9
Copy link

// 实现一个批量请求函数 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);
})

@JiaYifan
Copy link

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)
);

@JiaYifan
Copy link

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)
);

@haipingzi
Copy link

 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);
      });
    }

@z1948296027
Copy link

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时,第三个请求进来还是先执行了,是不是不对

@Y-J-H
Copy link

Y-J-H commented May 22, 2021

/**
 *  想法是先构造好请求的一个对象, 包含请求方法, 请求状态, 请求的响应, 通过传入的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)
})

@yakiang
Copy link

yakiang commented Jun 5, 2021

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);

@poppydani
Copy link

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

@tancgo
Copy link

tancgo commented Nov 11, 2021

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

@WebXiaoMing
Copy link

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)
})

@hcd1983
Copy link

hcd1983 commented Dec 13, 2021

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 把結果壓平

@Jobs-Chen
Copy link

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();
  }
}

@Yangfan2016
Copy link

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)
})

@qiqingfu
Copy link

qiqingfu commented Aug 30, 2022

控制异步请求的并行个数,内部通过 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

@YOUNGmaxer
Copy link

YOUNGmaxer commented Oct 7, 2022

/**
 * 带并发限制的批量请求函数
 * @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);
    });
  }
}

@SlahserZ
Copy link

// 这题如果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++位置不对,它应该是在执行成功或者失败的回调里。

@lqt0223
Copy link

lqt0223 commented Mar 30, 2023

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
*/

@simei1
Copy link

simei1 commented Sep 23, 2023

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),
])

@keven-wang
Copy link

最烦这种题目,弯弯绕绕的地方不少,但实际项目中屌用没用。题目其实有个障眼法:那个最大连接数,这个只有在第一次请求时才有效,后面多个请求是分别返回的,不存在并发的情况(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);  
});

@chens01
Copy link

chens01 commented May 20, 2024

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

async function multiRequest(urls, maxNum) {
  let promises = [];
  let result = [];
  let i = 0;

  function l(id) {
    return ajax(urls[id]).then(res => {
      result[id] = res;
    });
  };

  while(i < maxNum) {
    promises.push(l(i));
    i++;
  }

  while(i < urls.length) {
    await Promise.race(promises).then(() => {
      promises.push(l(i));
      i++;
    })
  }

  return Promise.all(promises).then(() => {
    console.log(result);
    return result;
  });
}

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

@negativeentropy9
Copy link

function multiRequest(urls, maxNum) {
    return new Promise(resolve => {  
        console.time('total response time');

        const startTime = performance.now();
        const result = [];
        let requestCount = 0;
        let responseCount = 0;

        for(let i = 0; i < Math.min(maxNum, urls.length); i++) {
            fetchData(i);
        }

        async function fetchData(i) {
            console.log('afer', performance.now() - startTime, 'ms, request ', urls[i]);
            
            requestCount++;

            try {
                const data = await createPromise(i);

                result[i] = data;
            } catch(e) {
                // TODO 处理错误
                result[i] = e;
            }

            responseCount++;

            console.log('afer', performance.now() - startTime, 'ms, response ', urls[i]);

            if(requestCount < urls.length) {
                fetchData(requestCount);
            }

            if(responseCount === urls.length) {
                console.timeEnd('total response time')
                resolve(result);
            }
        }
    });
}

function createPromise(url) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if(url == 5) {
                reject(new Error(url));
            } else {
                resolve(url);
            }
        }, (10 - url) * 1000 );
    });
}

multiRequest(Array.from({length: 10}, (_, i) => i + 1), 5).then((data) => {
    console.log(data);
});

@mileOfSunshine
Copy link

async function multiRequest(urls, maxNum) {
    const rets = []
    let index = 0
    let resolved
    let size = urls.length
    
    async function addTask(urls, maxNum) {
        const url = urls.shift()
        index++

        function next(urls, index) {
            if (urls.length) {
                if (index < maxNum) {
                    addTask(urls, maxNum)
                }
            } else if (size === rets.length) {
                resolved(rets)
            }
        }
        
        fetch(url).then(res => {
            rets.push(res)
            index--
            next(urls, index)
        }).catch(res => {
            rets.push(res)
            index--
            next(urls, index)
        }).finally((res) => {
        })

        next(urls, index)
    }

    return new Promise((resolve, reject) => {
        resolved = resolve
        addTask(urls, maxNum)
    })
}

let url2 = 'https://api.github.com/search/users?q=d';
let arr = new Array(10).fill(url2).map((v, i) => `${v}&i=${i}`)
multiRequest(arr, 2).then((res) => {
    console.log(res)
})

@DarthVaderrr
Copy link

async function multiRequests(urls, maxNum) {
    const res = new Array(urls.length);
    const executing = [];
    for (let i in urls) {
        if (executing.length === maxNum) {
            await Promise.race(executing)
        }
        
        const url = urls[i]
        const promiseTask = fetch(url);
        promiseTask.then((data) => {
            res[i] = data;
            const index = executing.findIndex(item => item === promiseTask);
            executing.splice(index, 1)
        })
        executing.push(promiseTask);
    }
    await Promise.all(executing);
    return res;
} 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests