Javascript 获取在promise.race中完成的承诺
上下文:我需要进行大量可并行化的异步调用(大约300到3000个ajax调用)。但是,我不想一次调用所有浏览器或服务器,从而使浏览器或服务器感到紧张。我也不想按顺序运行它们,因为要花很长时间才能完成。我决定一次运行五个左右,并派生了此函数: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
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);
}
}
}