繁体   English   中英

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

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

我有一系列的诺言,我想并行调用,但要同步解决。

我做了一些代码来完成所需的任务,但是,我需要创建自己的对象QueryablePromise来包装本地Promise ,我可以对其进行同步检查以查看其已解决的状态。

有没有更好的方法可以完成不需要特殊对象的任务?

请注意。 我不想使用Promise.all因为我不想在处理承诺的效果之前等待所有的承诺解决。 而且我无法在代码库中使用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> 

为任何帮助加油。

使用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}`); } })(); 

由于不能使用异步函数,因此可以使用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}`); }); 

错误处理

由于承诺被声明为并行实例化,因此我将假设任何单个承诺的错误都不会传播到其他承诺,除非通过asyncForEach()构建的任何潜在的易碎链(如上)。

但是,当在asyncForEach()中将asyncForEach()链接在一起时,我们还希望避免promise之间的交叉传播错误。 这是一种可靠地安排错误回调的方法,其中错误只能从原始的承诺中传播出去:

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

这里唯一需要注意的是,传递给asyncForEach()的回调中引发的任何错误都将被链中的错误处理所吞噬,除了在数组的最后一个索引中的回调中引发的错误之外。

我建议确实使用Promise.all但不要同时使用所有的Promise.all ,而是要在每个步骤中都实现的所有Promise.all 您可以使用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