簡體   English   中英

獲取在 Promise.race 中完成的承諾

[英]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])));

這為隊列中的每個元素創建了一個承諾,當承諾完成時返回承諾。 然后通過比賽,你會得到第一個完成的承諾。

最初completedp周圍沒有括號。 由於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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM