Сделайте несколько запросов к API, который может обрабатывать только 20 запросов в минуту
У меня есть метод, который возвращает обещание, и внутренне этот метод вызывает API, который может иметь только 20 запросов в минуту. Проблема в том, что у меня есть большой массив объектов (около 300), и я хотел бы сделать вызов API для каждого из них.
На данный момент у меня есть следующий код:
const bigArray = [.....];
Promise.all(bigArray.map(apiFetch)).then((data) => {
...
});
Но это не обрабатывает временные ограничения. Я надеялся, что смогу использовать что-то вроде _.chunk и _.debounce от lodash
но я не могу сосредоточиться на этом. Кто-нибудь может мне помочь?
2 ответа
Вы можете отправлять 1 блок из 20 запросов каждую минуту или распределять их по 1 запросу каждые 3 секунды (последний, вероятно, предпочитают владельцы 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);
}
})();
}
Для вызова 1 каждые 3 секунды:
rateLimitedRequests(bigArray, 1);
Или 20 каждую минуту:
rateLimitedRequests(bigArray, 20);
Если вы предпочитаете использовать _.chunk
а также _.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 Вы, вероятно, хотите _.throttle
так как он выполняет каждый вызов функции после задержки, тогда как _.debounce
группирует несколько звонков в один звонок. Смотрите эту статью по ссылкам из документов
Debounce: думайте об этом как о "группировании нескольких событий в одном". Представьте, что вы идете домой, входите в лифт, двери закрываются... и вдруг ваш сосед появляется в коридоре и пытается прыгнуть на лифте. Будьте вежливы! и откройте двери для него: вы отбрасываете лифт отъезда. Учтите, что та же самая ситуация может повториться с третьим лицом, и так далее... возможно, задержка отъезда на несколько минут.
Дроссель: Думайте об этом как о клапане, он регулирует поток казней. Мы можем определить максимальное количество раз, которое функция может быть вызвана в определенное время. Так что по аналогии с лифтом... вы достаточно вежливы, чтобы впустить людей на 10 секунд, но как только эта задержка пройдет, вы должны идти!
Если вы можете использовать библиотеку обещаний Bluebird, в нее встроена функция параллелизма, которая позволяет вам управлять группой асинхронных операций максимум для N в полете за раз.
var Promise = require('bluebird');
const bigArray = [....];
Promise.map(bigArray, apiFetch, {concurrency: 20}).then(function(data) {
// all done here
});
Приятной особенностью этого интерфейса является то, что он будет хранить 20 запросов в полете. Это начнется 20, затем каждый раз, когда один заканчивается, он запускает другой. Таким образом, это потенциально более эффективно, чем отправка 20, ожидание завершения, отправка еще 20 и т. Д.
Это также обеспечивает результаты в том же порядке, что и bigArray
так что вы можете определить, какой результат соответствует какому запросу.
Конечно, вы можете кодировать это самостоятельно с помощью общих обещаний, используя счетчик, но, поскольку он уже встроен в библиотеку Bluebird, я подумал, что рекомендую этот способ.
Библиотека Async также имеет аналогичный элемент управления параллелизмом, хотя, очевидно, она не основана на обещаниях.
Вот версия с ручным кодированием, использующая только обещания ES6, которые поддерживают порядок результатов и постоянно поддерживают 20 запросов (пока не осталось 20) для максимальной пропускной способности:
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
});
Рабочая демонстрация здесь: http://jsfiddle.net/jfriend00/v98735uu/