Faça várias solicitações para uma API que só pode manipular 20 solicitações por minuto

Eu tenho um método que retorna uma promise e internamente esse método faz uma chamada para uma API que só pode ter 20 solicitações a cada minuto. O problema é que tenho uma grande variedade de objects (por volta de 300) e gostaria de fazer uma chamada para a API para cada um deles.

No momento, tenho o seguinte código:

const bigArray = [.....]; Promise.all(bigArray.map(apiFetch)).then((data) => { ... }); 

Mas não lida com a restrição de tempo. Eu estava esperando que eu pudesse usar algo como _.chunk e _.debounce de lodash mas eu não posso envolver minha mente em torno disso. Alguém poderia me ajudar ?

Você poderia enviar 1 bloco de 20 solicitações a cada minuto ou espaçá-las 1 solicitação a cada 3 segundos (esta última provavelmente preferida pelos proprietários da API).

 function rateLimitedRequests(array, chunkSize) { var delay = 3000 * chunkSize; var remaining = array.length; var promises = []; var addPromises = function(newPromises) { Array.prototype.push.apply(promises, newPromises); if (remaining -= newPromises.length == 0) { Promise.all(promises).then((data) => { ... // do your thing }); } }; (function request() { addPromises(array.splice(0, chunkSize).map(apiFetch)); if (array.length) { setTimeout(request, delay); } })(); } 

Para ligar 1 a cada 3 segundos:

 rateLimitedRequests(bigArray, 1); 

Ou 20 a cada minuto:

 rateLimitedRequests(bigArray, 20); 

Se você preferir usar _.chunk e _.debounce 1 _.throttle :

 function rateLimitedRequests(array, chunkSize) { var delay = 3000 * chunkSize; var remaining = array.length; var promises = []; var addPromises = function(newPromises) { Array.prototype.push.apply(promises, newPromises); if (remaining -= newPromises.length == 0) { Promise.all(promises).then((data) => { ... // do your thing }); } }; var chunks = _.chunk(array, chunkSize); var throttledFn = _.throttle(function() { addPromises(chunks.pop().map(apiFetch)); }, delay, {leading: true}); for (var i = 0; i < chunks.length; i++) { throttledFn(); } } 

1 Você provavelmente vai querer o _.throttle uma vez que ele executa cada chamada de function após um atraso, enquanto o _.debounce agrupa múltiplas chamadas em uma chamada. Veja este artigo ligado a partir dos documentos

Debounce : Pense nisso como "agrupar vários events em um". Imagine que você vai para casa, entra no elevador, as portas estão fechando ... e de repente seu vizinho aparece no corredor e tenta pular no elevador. Seja educado! e abra as portas para ele: você está debitando a partida do elevador. Considere que a mesma situação pode acontecer novamente com uma terceira pessoa, e assim por diante ... provavelmente atrasando a partida em vários minutos.

Acelerador : Pense nisso como uma válvula, ele regula o stream das execuções. Podemos determinar o número máximo de vezes que uma function pode ser chamada em determinado tempo. Então, na analogia do elevador ... você é educado o suficiente para deixar as pessoas entrarem por 10 segundos, mas uma vez que o atraso passar, você deve ir!

Se você pode usar a biblioteca promissora Bluebird, ela possui um recurso de simultaneidade que permite gerenciar um grupo de operações assíncronas para no máximo N em vôo de cada vez.

 var Promise = require('bluebird'); const bigArray = [....]; Promise.map(bigArray, apiFetch, {concurrency: 20}).then(function(data) { // all done here }); 

O legal dessa interface é que ela manterá 20 solicitações em vôo. Começará 20, então cada vez que um terminar, começará outro. Então, isso é potencialmente mais eficiente do que enviar 20, esperando que todos terminem, enviando mais 20, etc …

Isso também fornece os resultados na mesma ordem exata do bigArray para que você possa identificar qual resultado combina com a solicitação.

Você poderia, é claro, codificar isso com promises genéricas usando um contador, mas como ele já é construído na biblioteca do Bluebird, eu pensei em recomendar isso.

A biblioteca Async também tem um controle de simultaneidade similar, embora obviamente não seja baseado em promises.


Aqui está uma versão codificada à mão usando apenas promises do ES6 que mantém a ordem dos resultados e mantém 20 solicitações em andamento o tempo todo (até que não haja mais 20) para obter o máximo de rendimento:

 function pMap(array, fn, limit) { return new Promise(function(resolve, reject) { var index = 0, cnt = 0, stop = false, results = new Array(array.length); function run() { while (!stop && index < array.length && cnt < limit) { (function(i) { ++cnt; ++index; fn(array[i]).then(function(data) { results[i] = data; --cnt; // see if we are done or should run more requests if (cnt === 0 && index === array.length) { resolve(results); } else { run(); } }, function(err) { // set stop flag so no more requests will be sent stop = true; --cnt; reject(err); }); })(index); } } run(); }); } pMap(bigArray, apiFetch, 20).then(function(data) { // all done here }, function(err) { // error here }); 

Demonstração de trabalho aqui: http://jsfiddle.net/jfriend00/v98735uu/