Используя цикл for await...of
, вы можете сделать это довольно хорошо, если у вас уже есть массив обещаний:
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}`);
}
})();
Поскольку вы не можете использовать асинхронные функции, вы можете имитировать эффект for await...of
, объединяя в цепочку обещания, используя Array.prototype.reduce()
, иСинхронное планирование обратного вызова для каждой цепочки:
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()
.Вот способ надежно планировать обратные вызовы ошибок, когда ошибки могут распространяться только из исходных обещаний:
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()
, будут поглощены обработкой ошибок в цепочке, за исключением ошибок, возникающих вобратные вызовы последнего индекса массива.