簡體   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