Javascript 如何将Promise.all()限制为每秒5个承诺?

Javascript 如何将Promise.all()限制为每秒5个承诺?,javascript,node.js,promise,es6-promise,throttling,Javascript,Node.js,Promise,Es6 Promise,Throttling,我有几个项目需要查询第三方API,并且说API的调用限制为每秒5次。我需要将对API的调用限制在每秒最多5次 到目前为止,我只是在承诺数组中使用了Promise.all(),其中每个承诺向API发送一个请求,并在API使用HTTP状态代码200响应时解析,在使用其他状态代码响应时拒绝。但是,当数组中的项目超过5个时,我会冒着Promise.all()拒绝的风险 如何将Promise.all()调用限制为每秒5次?我希望这对您有所帮助 也可以说,这将使用Promise.all来解决所有请求,如果您

我有几个项目需要查询第三方API,并且说API的调用限制为每秒5次。我需要将对API的调用限制在每秒最多5次

到目前为止,我只是在承诺数组中使用了
Promise.all()
,其中每个承诺向API发送一个请求,并在API使用HTTP状态代码
200
响应时解析,在使用其他状态代码响应时拒绝。但是,当数组中的项目超过5个时,我会冒着
Promise.all()
拒绝的风险


如何将
Promise.all()
调用限制为每秒5次?

我希望这对您有所帮助

也可以说,这将使用
Promise.all
来解决所有请求,如果您有一个很大的查询列表,这将等待所有请求都得到解决,并且可能会导致代码中出现大量等待以获得所有响应的情况。 如果其中一个请求被拒绝,
Promise.all将被拒绝

我建议,如果您不需要将所有结果放在一起,最好使用lodash或框架之类的其他工具来处理这些结果

let items = [
    {name: 'item1'}, 
    {name: 'item2'}, 
    {name: 'item3'}, 
    {name: 'item4'}, 
    {name: 'item5'}, 
    {name: 'item6'}
];

// This is the api request that you send and return a promise
function apiCall(item) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(item.name), 1000);
  })
}

new Promise((resolve) => {
  let results = [];

  function sendReq (itemsList, iterate, apiCall) {
    setTimeout(() => {
      // slice itemsList to send request according to the api limit
      let slicedArray = itemsList.slice(iterate * 5, (iterate * 5 + 5));
      result = slicedArray.map(item => apiCall(item));
      results = [...results, ...result];

      // This will resolve the promise when reaches to the last iteration
      if (iterate === Math.ceil(items.length / 5) - 1) {
          resolve(results);
      }
    }, (1000 * iterate)); // every 1000ms runs (api limit of one second)
  }

  // This will make iteration to split array (requests) to chunks of five items 
  for (i = 0; i < Math.ceil(items.length / 5); i++) {
    sendReq(items, i, apiCall);
  }
}).then(Promise.all.bind(Promise)).then(console.log);
// Use Promise.all to wait for all requests to resolve
// To use it this way binding is required
let项目=[
{name:'item1'},
{name:'item2'},
{name:'item3'},
{name:'item4'},
{name:'item5'},
{name:'item6'}
];
//这是您发送并返回承诺的api请求
所有功能(项目){
返回新承诺((解决)=>{
setTimeout(()=>resolve(item.name),1000);
})
}
新承诺((决议)=>{
让结果=[];
函数sendReq(itemsList、iterate、apiCall){
设置超时(()=>{
//slice itemsList根据api限制发送请求
设slicedArray=itemsList.slice(迭代*5,(迭代*5+5));
结果=slicedArray.map(item=>apiCall(item));
结果=[…结果,…结果];
//这将在到达最后一次迭代时解决承诺
if(iterate===Math.ceil(items.length/5)-1){
决心(结果);
}
},(1000*迭代));//每1000ms运行一次(api限制为1秒)
}
//这将使迭代将数组(请求)拆分为五个项目的块
对于(i=0;i
如果您不太担心按顺序解决承诺,可以使用bluebird中的并发选项

以下内容一次只能处理5个查询

const Promise = require('bluebird');

const buildQueries = (count) => {
  let queries = [];

  for(let i = 0; i < count; i++) {
    queries.push({user: i});
  };

  return queries;
};

const apiCall = (item) => {
  return new Promise(async (resolve, reject) => {
    await Promise.delay(1000);
    resolve(item.user);
  });
};

const queries = buildQueries(20);

Promise.map(queries, async query => {
  console.log( await apiCall(query) );
}, {concurrency: 5});
const Promise=require('bluebird');
const buildquerys=(计数)=>{
让查询=[];
for(设i=0;i{
返回新承诺(异步(解析、拒绝)=>{
等待承诺。延迟(1000);
解决(项目.用户);
});
};
const querys=buildquerys(20);
map(查询,异步查询=>{
log(等待apiCall(查询));
},{并发:5});

使用不带库的ES6

export async function asyncForEach(array, callback) {
  for (let index = 0; index < array.length; index++) {
    await callback(array[index], index, array);
  }
}
export function split(arr, n) {
  var res = [];
  while (arr.length) {
    res.push(arr.splice(0, n));
  }
  return res;
}
export const delayMS = (t = 200) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(t);
    }, t);
  });
};
export const throttledPromises = (
  asyncFunction,
  items = [],
  batchSize = 1,
  delay = 0
) => {
  return new Promise(async (resolve, reject) => {
    const output = [];
    const batches= split(items, batchSize);
    await asyncForEach(batches, async (batch) => {
      const promises = batch.map(asyncFunction).map(p => p.catch(reject));
      const results = await Promise.all(promises);
      output.push(...results);
      await delayMS(delay);
    });
    resolve(output);
  });
};
导出异步函数asyncForEach(数组、回调){
for(让index=0;index{
返回新承诺(解决=>{
设置超时(()=>{
决心(t);
},t);
});
};
导出常量限制承诺=(
异步函数,
项目=[],
batchSize=1,
延迟=0
) => {
返回新承诺(异步(解析、拒绝)=>{
常量输出=[];
常量批次=拆分(项目、批次大小);
等待asyncForEach(批处理,异步(批处理)=>{
const promises=batch.map(异步函数).map(p=>p.catch(拒绝));
const results=等待承诺。全部(承诺);
输出。推送(…结果);
等待延迟ms(延迟);
});
解析(输出);
});
};

我认为您可以将问题分为两部分:一次不超过5个呼叫,并确保最新呼叫在最长呼叫的1秒后才发生

第一部分很容易用一个惊人的库来解决——它有我见过的最简单的接口

对于第二部分,您需要实际跟踪每个调用何时开始-即实现等待功能: 基本伪代码,尚未测试:

import pLimit from 'p-limit';
const apiLimit = pLimit(5);

const startTimes = [];

async function rateLimiter(item) {
  const lastSecond = (new Date().getTime()) - 1000;
  if (startTimes.filter(v => v > lastSecond).length >= 5) {
    await new Promise(r => setTimeout(r, 1000));
  }
  // TODO: cleanup startTimes to avoid memory leak
  startTimes.push(new Date().getTime());
  return apiCall(item);
}

await Promise.all(items.map(v => apiLimit(() => rateLimiter(v))))

我们可以使用生成器以组的形式发送承诺列表。 一旦解决了第一个收益率,我们就可以进行另一个收益率。 我们将结果存储在一个数组中。 一旦promiseArray长度等于结果长度,我们就可以解析 包装好的承诺

const fetch = require("isomorphic-fetch");
const totalPromiseLength = 5;
const requestMethod = url => () => fetch(url).then(response => response.json());
let promiseArray = [...new Array(totalPromiseLength).keys()].map(index =>
  requestMethod("https://jsonplaceholder.typicode.com/todos/" + (index + 1))
);
function* chunks(arr, limit) {
  for (let i = 0; i < Math.ceil(arr.length / limit); ++i) {
    yield [...arr].slice(i * limit, i * limit + limit);
  }
}

new Promise(async resolve => {
  let generated = chunks(promiseArray, 2);
  let result = [];
  for (let bla of generated) {
    await Promise.all(bla.map(param => param())).then(response => {
      result = [...result, ...response];
      if (result.length === promiseArray.length) {
        resolve(result);
      }
    });
  }
}).then(response => {
  console.log(response);
});

const fetch=require(“同构fetch”);
const totalPromiseLength=5;
constrequestmethod=url=>()=>fetch(url).then(response=>response.json());
让promiseArray=[…新数组(totalPromiseLength).keys()].map(索引=>
请求方法(“https://jsonplaceholder.typicode.com/todos/“+(索引+1))
);
函数*块(arr、限制){
for(设i=0;i{
let generated=块(promiseArray,2);
让结果=[];
对于(让bla生成){
等待Promise.all(bla.map(param=>param())。然后(response=>{
结果=[…结果,…响应];
if(result.length==promiseArray.length){
决心(结果);
}
});
}
})。然后(响应=>{
控制台日志(响应);
});

也许我头脑简单,但我编写的这个版本只是将传入数组分成5个Promise块,每个块上都有Promise.all():

utility.throttledPromiseAll = async (promises) => {
  const MAX_IN_PROCESS = 5;
  const results = new Array(promises.length);

  async function doBlock(startIndex) {
    // Shallow-copy a block of promises to work on
    const currBlock = promises.slice(startIndex, startIndex + MAX_IN_PROCESS);
    // Await the completion. If any fail, it will throw and that's good.
    const blockResults = await Promise.all(currBlock);
    // Assuming all succeeded, copy the results into the results array
    for (let ix = 0; ix < blockResults.length; ix++) {
      results[ix + startIndex] = blockResults[ix];
    }
  }

  for (let iBlock = 0; iBlock < promises.length; iBlock += MAX_IN_PROCESS) {
    await doBlock(iBlock);
  }
  return results;
};
utility.throttledPromiseAll=异步(承诺)=>{
const MAX_IN_PROCESS=5;
const results=新数组(promissions.length);
异步函数doBlock(startIndex){
//浅薄地复制一份对工作的承诺