[英]Get which promise completed in Promise.race
上下文:我需要進行大量可並行化的異步調用(想想大約 300 到 3000 次 ajax 調用)。 但是,我不想通過一次調用它們來使瀏覽器或服務器緊張。 我也不想順序運行它們,因為完成需要很長時間。 我決定一次運行五個左右,並派生出這個函數來做到這一點:
async function asyncLoop(asyncFns, concurrent = 5) {
// queue up simultaneous calls
let queue = [];
for (let fn of asyncFns) {
// fire the async function and add its promise to the queue
queue.push(fn());
// if max concurrent, wait for the oldest one to finish
if (queue.length >= concurrent) {
await queue.shift();
}
}
// wait for the rest of the calls to finish
await Promise.all(queue);
};
其中 asyncFns 是(尚未調用的)異步函數的可迭代對象。
問題:這可行,但是我發現最舊的第一個完成並不總是正確的。 我想修改該函數,使其使用Promise.race等待第一個承諾成功,然后從那里繼續。 然而,我不知道要刪除哪個承諾:
// if max concurrent, wait for the first one to finish
if (queue.length >= concurrent) {
await Promise.race(queue);
// ??? get race's completed promise
// queue.splice(queue.indexOf(completed), 1);
}
如果我只知道哪個完成的索引,我可以將它從隊列中拼接出來(我猜現在更像是一個集合)。 看起來我無法從種族返回的派生承諾中獲得原始承諾。 建議?
感謝@Dan D. 在發布后不久刪除了他們的答案:
let [completed] = await Promise.race(queue.map(p => p.then(res => [p])));
這為隊列中的每個元素創建了一個承諾,當承諾完成時返回承諾。 然后通過比賽,你會得到第一個完成的承諾。
最初completed
或p
周圍沒有括號。 由於p
是一個承諾並且有一個then
方法,這個承諾被再次鏈接起來,返回承諾的已解析值而不是承諾(因此它不起作用)。 我想這就是答案被刪除的原因。 通過將 Promise 包裝在一個數組中,然后使用Array Destructuring assignment ,您可以防止它再次鏈接,從而獲得 Promise。
“從隊列中刪除”步驟應該由已完成的承諾本身(使用then
)發生,而不是依賴於Promise.race
返回的承諾。 看來這是唯一的辦法了。
async function asyncLoop(asyncFns, concurrent = 5) {
// queue up simultaneous calls
let queue = [];
let ret = [];
for (let fn of asyncFns) {
// fire the async function, add its promise to the queue, and remove
// it from queue when complete
const p = fn().then(res => {
queue.splice(queue.indexOf(p), 1);
return res;
});
queue.push(p);
ret.push(p);
// if max concurrent, wait for one to finish
if (queue.length >= concurrent) {
await Promise.race(queue);
}
}
// wait for the rest of the calls to finish
await Promise.all(queue);
};
Npm 模塊: https : //github.com/rxaviers/async-pool
而不是單個隊列,為什么沒有 5 個“串行”隊列
async function asyncLoop(asyncFns, concurrent = 5) {
const queues = new Array(concurrent).fill(0).map(() => Promise.resolve());
let index = 0;
const add = cb => {
index = (index + 1) % concurrent;
return queues[index] = queues[index].then(() => cb());
};
let results = [];
for (let fn of asyncFns) {
results.push(add(fn));
}
await Promise.all(results);
};
好的......首先,它並不漂亮,但它似乎工作 - 但是,這假設asyncFns
是一個數組- 使用Object.values
可能很容易“修復”對象
const asyncLoop = (asyncFns, concurrent = 5) => { let inFlight = 0; let pending = []; const end = result => { inFlight--; var job = pending.shift(); job && job(); return result; }; const begin = (fn) => { if (inFlight < concurrent) { inFlight++; return fn(); } let resolver; const promise = new Promise(resolve => { resolver = () => { inFlight ++; resolve(fn()); } }); pending.push(resolver); return promise; } return Promise.all(asyncFns.map(fn => begin(fn).then(end))); }; const fns = new Array(25).fill(0).map((v, index) => () => new Promise(resolve => { let timeout = 1000; if (index == 6 || index == 11) { timeout = 2000; } setTimeout(resolve, timeout, index); })); console.time('timeToComplete'); asyncLoop(fns, 5).then(result => { console.timeEnd('timeToComplete'); console.log(JSON.stringify(result)); });
我想要類似的東西,但我對這些答案中的任何一個都不滿意。
這是我想出的。 它不能完全回答您的問題,但可能會幫助您完成部分工作。
它使用類似於 Jonathan Gawrych 的答案。
也許這會幫助別人:
/**
* Used like:
* dealWithPromisesAsTheyResolve([
* new Promise((res, rej) => setTimeout(res, 2000, 2000)),
* new Promise((res, rej) => setTimeout(res, 1000, 1000)),
* new Promise((res, rej) => setTimeout(res, 4000, 4000)),
* new Promise((res, rej) => setTimeout(res, 0, 0)),
* new Promise((res, rej) => setTimeout(rej, 3000, 3000)),
* ], num => console.log(num), err => console.log(`error: ${err}`));
*
* Will output:
* 0
* 1000
* 2000
* error: 3000
* 4000
*/
async function dealWithPromisesAsTheyResolve(promises, resolveCallback, rejectCallback) {
var _promises = new Map();
promises.forEach(promise => _promises.set(
promise,
promise
.then(value => [null, value, promise])
.catch(error => [error, null, promise])
));
while (_promises.size > 0) {
let [error, value, promise] = await Promise.race(_promises.values());
_promises.delete(promise);
if (error) {
rejectCallback(error);
} else {
resolveCallback(value);
}
}
}
您可以修改它以接受限制並在每次完成時添加新的承諾。
這是一個簡約的實現,它返回贏得Promise.race
的承諾。 它使用 JavaScript 迭代器,因此它不會創建新的數組/映射:
/** * When any promise is resolved or rejected, * returns that promise as the result. * @param {Iterable.<Promise>} iterablePromises An iterable of promises. * @return {{winner: Promise}} The winner promise. */ async function whenAny(iterablePromises) { let winner; await Promise.race(function* getRacers() { for (const p of iterablePromises) { if (!p?.then) throw new TypeError(); const settle = () => winner = winner ?? p; yield p.then(settle, settle); } }()); // return the winner promise as an object property, // to prevent automatic promise "unwrapping" return { winner }; } // test it function createTimeout(ms) { return new Promise(resolve => setTimeout(() => resolve(ms), ms)); } async function main() { const p = createTimeout(500); const result = await whenAny([ createTimeout(1000), createTimeout(1500), p ]); console.assert(result.winner === p); console.log(await result.winner); } main().catch(e => console.warn(`caught on main: ${e.message}`));
完全受到 Jonathan Gawrych 的啟發,這是我為處理無限流的 promise所做的事情,我希望其中10 個始終並行運行。
async function* concurrentResolver(promisesIter, numInParallel) {
const pending = [];
for (let i = 0; i < numInParallel; i++) {
const next = promisesIter.next();
if (next.done) {
break;
}
pending.push(next.value);
}
while (pending.length) {
const darkMagic = pending.map((p) => p.then((_) => [p]));
const [promise] = await Promise.race(darkMagic);
pending.splice(pending.indexOf(promise), 1);
const next = promisesIter.next();
if (!next.done) {
pending.push(next.value);
}
// the following `await` is instantaneous, since
// the promise has already been resolved.
yield await promise;
}
}
這里有一些代碼來測試它:
function* promisesGenerator({ howMany, timeEachResolves }) {
for (let i = 0; i < howMany; i++) {
yield new Promise((res) =>
setTimeout(res, timeEachResolves, "fake server res")
);
}
}
const promisesIter = promisesGenerator({ howMany: 30, timeEachResolves: 3000 });
const numInParallel = 10;
for await (const res of concurrentResolver(promisesIter, numInParallel)) {
console.log(`at ${new Date().toLocaleTimeString()}: ${res}`);
}
/*
Output from Chrome's console:
(10) at 7:06:44 PM: fake server res
(10) at 7:06:47 PM: fake server res
(10) at 7:06:50 PM: fake server res
*/
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.