サードパーティのAPIにクエリを実行する必要がある項目がいくつかあり、APIの呼び出し制限は1秒あたり5回です。 APIへの呼び出しを1秒あたり最大5回に制限する必要があります。
これまで、promiseの配列でPromise.all()
を使用しました。ここで、各promiseはAPIにリクエストを送信し、APIがHTTPステータスコード_200
_で応答すると解決し、応答すると拒否します。他のステータスコードを使用します。ただし、配列に5つを超える項目がある場合、Promise.all()
が拒否するリスクがあります。
Promise.all()
呼び出しを1秒あたり5回に制限するにはどうすればよいですか?
これがお役に立てば幸いです。
また、これはPromise.all
を使用してすべてのリクエストを解決し、クエリのリストが大きい場合は、すべてが解決されるのを待ち、コードですべての応答を取得するのを多く待つ可能性があります。また、リクエストの1つが拒否された場合、Promise.all
は拒否します。
すべての結果を一緒に必要としない場合は、lodash debounce または throttle またはこれを処理するフレームワークのような他のものを使用することをお勧めします。
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
約束を順番に解決することについてあまり心配していない場合は、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});
ライブラリなしで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);
});
};