Javascript 获取在promise.race中完成的承诺

Javascript 获取在promise.race中完成的承诺,javascript,asynchronous,async-await,es6-promise,Javascript,Asynchronous,Async Await,Es6 Promise,上下文:我需要进行大量可并行化的异步调用(大约300到3000个ajax调用)。但是,我不想一次调用所有浏览器或服务器,从而使浏览器或服务器感到紧张。我也不想按顺序运行它们,因为要花很长时间才能完成。我决定一次运行五个左右,并派生了此函数: async function asyncLoop(asyncFns, concurrent = 5) { // queue up simultaneous calls let queue = []; for (let fn of a

上下文:我需要进行大量可并行化的异步调用(大约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是异步函数的一个iterable(尚未调用)

问题:这是可行的,但是我发现最老的并不总是第一个完成的。我想修改这个函数,让它使用它来等待第一个承诺成功,然后从那里继续。然而,我不知道该取消哪项承诺:

        // 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);
        }

如果我只知道完成了哪一个的索引,我就可以把它从队列中剪接出来(我想现在更像是一个集合)。看起来我无法从race返回的派生承诺中获得原始承诺。建议?

为什么不使用5个“串行”队列,而不是单个队列

好的。。。首先,它并不漂亮,但似乎可以工作-但是,这假设
asyncFns
是一个数组-使用
Object.values

constasyncloop=(asyncFns,concurrent=5)=>{
让机上=0;
let pending=[];
const end=result=>{
机上--;
var job=pending.shift();
作业&&job();
返回结果;
};
常量开始=(fn)=>{
如果(机上<并发){
机上++;
返回fn();
}
让分解器;
常量承诺=新承诺(解析=>{
解析程序=()=>{
机上++;
解析(fn());
}
});
未决推送(解析器);
回报承诺;
}
返回Promise.all(asyncFns.map(fn=>begin(fn).then(end));
};
const fns=新数组(25).填充(0).映射((v,索引)=>()=>新承诺(解析=>{
设超时=1000;
如果(指数=6 | |指数=11){
超时=2000;
}
setTimeout(解析、超时、索引);
}));
控制台时间(“完成时间”);
异步循环(fns,5)。然后(结果=>{
console.timeEnd('timeToComplete');
log(JSON.stringify(result));

});归功于@Dan D。他在发布后不久删除了他们的答案:

let [completed] = await Promise.race(queue.map(p => p.then(res => [p])));
这将为队列中的每个元素创建一个承诺,当承诺完成时,该承诺将返回承诺。然后通过比赛,你得到了第一次完成的承诺

最初,
completed
p
周围没有括号。由于
p
是一个承诺,并且有一个
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模块:

我想要类似的东西,但我对这些答案都不满意

这是我想到的。这并不能完全回答你的问题,但可能会帮助你实现部分目标

它使用了与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);
    }
  }
}

您可以修改它以接受一个限制,并在每次完成时添加一个新的承诺。

这里有一个最低限度的实现,它返回赢得承诺的承诺。race
。它使用JavaScript迭代器,因此不会创建新的数组/映射:

/**
*当任何承诺被解决或拒绝时,
*作为结果返回该承诺。
*@param{Iterable.}iterablebromisea一个Iterable的承诺。
*@return{{winner:Promise}}获胜者的承诺。
*/
异步函数whenAny(iterablePromises){
让胜利者;
wait Promise.race(函数*getRacers(){
用于(ITERABLEPROMIES的常数p){
如果(!p?.then)抛出新的TypeError();
常数结算=()=>winner=winner??p;
收益率p.then(结算,结算);
}
}());
//将赢家承诺作为对象属性返回,
//防止自动承诺“展开”
返回{winner};
}
//测试一下
函数createTimeout(毫秒){
返回新承诺(解决=>
setTimeout(()=>resolve(ms),ms));
}
异步函数main(){
const p=createTimeout(500);
const result=wait wheny([
创建超时(1000),
创建超时(1500),
P
]);
console.assert(result.winner==p);
console.log(等待结果.winner);
}

main().catch(e=>console.warn(`catch on main:${e.message}`))有趣的解决方案!我不会想到制作5串承诺链。不幸的是,并不是所有的呼叫都需要统一的时间(以及我为什么尝试使用race)。想象一下25个异步函数,24个需要1s,一个需要5s。我最初的解决方案是用5秒打5个电话,然后用4秒打其他20个电话,总共9个。此解决方案将有一个需要9秒的队列。如果队列真的在滚动,则需要6秒。但是,这确实比我原来的方法有所改进:如果两次呼叫间隔5秒,间隔6次呼叫,我原来的方法需要13秒,而这个解决方案需要9秒。是的,我知道答案中有一个缺陷-但是,你知道浏览器限制了同时请求的数量, right@JonathanGawrych-我想我已经想出了一个解决方案,我感觉蓝鸟承诺可能是一个解决方案,我相信你可以给它传递一个并发参数!!!(该死的我虚弱的头脑)看看我的想法是这样的:
const[idx,result]=wait promiser.map((promisesar.map)(Promise,idx)=>Promise.then((result)=>[idx,result]);
/**
 * 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);
    }
  }
}