简体   繁体   English

并行调用一个promise数组,但是可以按顺序解决它们,而无需等待其他promise解决

[英]Call an array of promises in parallel, but resolve them in order without waiting for other promises to resolve

I have an array of promises that I would like to call in parallel, but resolve synchronously. 我有一系列的诺言,我想并行调用,但要同步解决。

I made this bit of code to do the required task, however, I needed to create my own object QueryablePromise to wrap the native Promise that I can synchronously check to see it's resolved status. 我做了一些代码来完成所需的任务,但是,我需要创建自己的对象QueryablePromise来包装本地Promise ,我可以对其进行同步检查以查看其已解决的状态。

Is there any better way to achieve this task that doesn't require a special object? 有没有更好的方法可以完成不需要特殊对象的任务?

Please note. 请注意。 I do not want to use Promise.all as I don't want to have to wait for all promises to resolve before processing the effects of the promises. 我不想使用Promise.all因为我不想在处理承诺的效果之前等待所有的承诺解决。 And I cannot use async functions in my code base. 而且我无法在代码库中使用async函数。

 const PROMISE = Symbol('PROMISE') const tap = fn => x => (fn(x), x) class QueryablePromise { resolved = false rejected = false fulfilled = false constructor(fn) { this[PROMISE] = new Promise(fn) .then(tap(() => { this.fulfilled = true this.resolved = true })) .catch(x => { this.fulfilled = true this.rejected = true throw x }) } then(fn) { this[PROMISE].then(fn) return this } catch(fn) { this[PROMISE].catch(fn) return this } static resolve(x) { return new QueryablePromise((res) => res(x)) } static reject(x) { return new QueryablePromise((_, rej) => rej(x)) } } /** * parallelPromiseSynchronousResolve * * Call array of promises in parallel but resolve them in order * * @param {Array<QueryablePromise>} promises * @praram {Array<fn>|fn} array of resolver function or single resolve function */ function parallelPromiseSynchronousResolve(promises, resolver) { let lastResolvedIndex = 0 const resolvePromises = (promise, i) => { promise.then(tap(x => { // loop through all the promises starting at the lastResolvedIndex for (; lastResolvedIndex < promises.length; lastResolvedIndex++) { // if promise at the current index isn't resolved break the loop if (!promises[lastResolvedIndex].resolved) { break } // resolve the promise with the correct resolve function promises[lastResolvedIndex].then( Array.isArray(resolver) ? resolver[lastResolvedIndex] : resolver ) } })) } promises.forEach(resolvePromises) } const timedPromise = (delay, label) => new QueryablePromise(res => setTimeout(() => { console.log(label) res(label) }, delay) ) parallelPromiseSynchronousResolve([ timedPromise(20, 'called first promise'), timedPromise(60, 'called second promise'), timedPromise(40, 'called third promise'), ], [ x => console.log('resolved first promise'), x => console.log('resolved second promise'), x => console.log('resolved third promise'), ]) 
 <script src="https://codepen.io/synthet1c/pen/KyQQmL.js"></script> 

Cheers for any help. 为任何帮助加油。

Using a for await...of loop, you can do this quite nicely if you already have the array of promises: 使用for await...of循环,如果已经有了promise数组,则可以很好地做到这一点:

 const delay = ms => new Promise(resolve => { setTimeout(resolve, ms); }); const range = (length, mapFn) => Array.from({ length }, (_, index) => mapFn(index)); (async () => { const promises = range(5, index => { const ms = Math.round(Math.random() * 5000); return delay(ms).then(() => ({ ms, index })); }); const start = Date.now(); for await (const { ms, index } of promises) { console.log(`index ${index} resolved at ${ms}, consumed at ${Date.now() - start}`); } })(); 

Since you can't use asynchronous functions, you can mimic the effect of for await...of by chaining the promises together using Array.prototype.reduce() , and synchronously scheduling a callback for each chain: 由于不能使用异步函数,因此可以使用Array.prototype.reduce()将promise链接在一起,并为每个链同步调度回调,从而模拟for await...of的效果:

 const delay = ms => new Promise(resolve => { setTimeout(resolve, ms); }); const range = (length, mapFn) => Array.from({ length }, (_, index) => mapFn(index)); const asyncForEach = (array, cb) => array.reduce( (chain, promise, index) => chain.then( () => promise ).then( value => cb(value, index) ), Promise.resolve() ); const promises = range(5, index => { const ms = Math.round(Math.random() * 5000); return delay(ms).then(() => ms); }); const start = Date.now(); asyncForEach(promises, (ms, index) => { console.log(`index ${index} resolved at ${ms}, consumed at ${Date.now() - start}`); }); 

Error Handling 错误处理

Since the promises were stated to be instantiated in parallel, I'll assume that errors on any individual promise will not propagate to other promises except through any potentially brittle chains constructed via asyncForEach() (like above). 由于承诺被声明为并行实例化,因此我将假设任何单个承诺的错误都不会传播到其他承诺,除非通过asyncForEach()构建的任何潜在的易碎链(如上)。

But we also want to avoid cross-propagating errors between promises when chaining them together in asyncForEach() . 但是,当在asyncForEach()中将asyncForEach()链接在一起时,我们还希望避免promise之间的交叉传播错误。 Here's a way to schedule error callbacks robustly where errors can only propagate from the original promises: 这是一种可靠地安排错误回调的方法,其中错误只能从原始的承诺中传播出去:

 const delay = ms => new Promise(resolve => { setTimeout(resolve, ms); }); const maybe = p => p.then(v => Math.random() < 0.5 ? Promise.reject(v) : v); const range = (length, mapFn) => Array.from({ length }, (_, index) => mapFn(index)); const asyncForEach = (array, fulfilled, rejected = () => {}) => array.reduce( (chain, promise, index) => { promise.catch(() => {}); // catch early rejection until handled below by chain return chain.then( () => promise, () => promise // catch rejected chain and settle with promise at index ).then( value => fulfilled(value, index), error => rejected(error, index) ); }, Promise.resolve() ); const promises = range(5, index => { const ms = Math.round(Math.random() * 5000); return maybe(delay(ms).then(() => ms)); // promises can fulfill or reject }); const start = Date.now(); const settled = state => (ms, index) => { console.log(`index ${index} ${state}ed at ${ms}, consumed at ${Date.now() - start}`); }; asyncForEach( promises, settled('fulfill'), settled('reject') // indexed callback for rejected state ); 

The only caveat to note here is that any errors thrown in the callbacks passed to asyncForEach() will get swallowed by the error handling in the chain except for errors thrown within the callbacks on the last index of the array. 这里唯一需要注意的是,传递给asyncForEach()的回调中引发的任何错误都将被链中的错误处理所吞噬,除了在数组的最后一个索引中的回调中引发的错误之外。

I would recommend to indeed use Promise.all - but not on all promises at once, rather all the promises that you want to have fulfilled for each step. 我建议确实使用Promise.all但不要同时使用所有的Promise.all ,而是要在每个步骤中都实现的所有Promise.all You can create this "tree list" of promises with reduce : 您可以使用reduce创建诺言的“树形列表”:

 function parallelPromisesSequentialReduce(promises, reducer, initial) { return promises.reduce((acc, promise, i) => { return Promise.all([acc, promise]).then(([prev, res]) => reducer(prev, res, i)); }, Promise.resolve(initial)); } const timedPromise = (delay, label) => new Promise(resolve => setTimeout(() => { console.log('fulfilled ' + label + ' promise'); resolve(label); }, delay) ); parallelPromisesSequentialReduce([ timedPromise(20, 'first'), timedPromise(60, 'second'), timedPromise(40, 'third'), ], (acc, res) => { console.log('combining ' + res + ' promise with previous result (' + acc + ')'); acc.push(res); return acc; }, []).then(res => { console.log('final result', res); }, console.error); 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM