[英]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.