Promise.all()
Success requires every task to fulfill. One rejection rejects the whole group.
Interactive examples showing how each Promise combinator settles when async tasks resolve or reject at different times.
Each method receives the same set of Promises, but it waits for a different condition.
const rules = {
all: 'fulfills only when every Promise fulfills',
race: 'settles with the first Promise to finish',
any: 'fulfills when the first Promise fulfills'
};
console.table(rules);
Success requires every task to fulfill. One rejection rejects the whole group.
The first finished task decides the result, whether it fulfilled or rejected.
The first successful task wins. It rejects only when every task rejects.
The article uses upload and load operations. This demo uses timers with the same 50% success rule.
const upload = function (blob) {
let time = Math.round(100 + 500 * Math.random());
return new Promise((resolve, reject) => {
console.log(`run ${time}ms`);
if (Math.random() > 0.5) {
setTimeout(resolve, time, 'promise resolved ' + time + 'ms');
} else {
setTimeout(reject, time, 'promise rejected ' + time + 'ms');
}
});
};
const load = function (url) {
return upload(url);
};
With three independent 50% tasks, the success probability is 0.5 * 0.5 * 0.5 = 12.5%.
(async () => {
try {
let result = await Promise.all([upload(0), upload(1), upload(2)]);
console.log(result);
} catch (err) {
console.error(err);
}
})();
The output always corresponds to the shortest delay, whether the winning Promise fulfilled or rejected.
(async () => {
try {
let result = await Promise.race([load(0), load(1), load(2)]);
console.log(result);
} catch (err) {
console.error(err);
}
})();
With three independent 50% tasks, the success probability is 1 - 0.5 * 0.5 * 0.5 = 87.5%.
(async () => {
try {
let result = await Promise.any([load(0), load(1), load(2)]);
console.log(result);
} catch (err) {
console.error(err);
}
})();
Use Promise.all() to wait for both the request and a minimum display timer.
let getUserInfo = function (user) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve('Name: Zhang Xinxu'), Math.floor(400 * Math.random()));
});
};
let showUserInfo = function (user) {
return getUserInfo().then(info => {
console.log('User information', info);
return true;
});
};
let timeout = function (delay, result) {
return new Promise(resolve => {
setTimeout(() => resolve(result), delay);
});
};
const time = +new Date();
let showToast = function () {
console.log('show loading...');
};
let hideToast = function () {
console.log('hide loading' + (+new Date() - time));
};
showToast();
Promise.all([showUserInfo(), timeout(200)]).then(() => {
hideToast();
});
If the request finishes within 200 ms, no loading indicator appears. Otherwise, it appears for at least 200 ms.
let time = 0;
let showToast = function () {
time = +new Date();
console.log('show loading...');
};
let hideToast = function () {
console.log('hide loading' + (+new Date() - time));
};
let promiseUserInfo = showUserInfo();
Promise.race([promiseUserInfo, timeout(200)]).then((display) => {
if (!display) {
showToast();
Promise.all([promiseUserInfo, timeout(200)]).then(() => {
hideToast();
});
}
});
Race useful work against a timeout, then cancel the work if it did not finish quickly enough.
function timeout(delay) {
let cancel;
const wait = new Promise(resolve => {
const timer = setTimeout(() => resolve(false), delay);
cancel = () => {
clearTimeout(timer);
resolve(true);
};
});
wait.cancel = cancel;
return wait;
}
function doWork() {
const workFactor = Math.floor(600 * Math.random());
const work = timeout(workFactor);
const result = work.then(canceled => {
if (canceled) {
console.log('Work canceled');
} else {
console.log('Work done in', workFactor, 'ms');
}
return !canceled;
});
result.cancel = work.cancel;
return result;
}
function attemptWork() {
const work = doWork();
return Promise.race([work, timeout(300)]).then(done => {
if (!done) {
work.cancel();
}
return done ? 'Work complete!' : 'I gave up';
});
}
attemptWork().then(console.log);
When the active queue reaches the limit, Promise.race() waits for one task to finish before starting another.
async function batchRequests(options) {
let nextBatch = 1;
let promises = [];
while (nextBatch <= options.totalBatches) {
const batchNumber = nextBatch++;
const promise = doLongRequestForBatch(batchNumber).then(() => {
promises = promises.filter(p => p !== promise);
});
promises.push(promise);
if (promises.length >= options.concurrentBatches) {
await Promise.race(promises);
}
}
return Promise.all(promises);
}
batchRequests({ totalBatches: 8, concurrentBatches: 3 });
Request the same resource through multiple paths and use the first successful response.
let startTime = +new Date();
let importUnpkg = loadResource('unpkg', 715, true);
let importJsdelivr = loadResource('jsDelivr', 83, true);
Promise.any([importUnpkg, importJsdelivr]).then(resource => {
console.log('Loading complete. Time: ' + (+new Date() - startTime) + 'ms');
console.log(resource.version);
});
Promise.any() arrived later than Promise.all() and Promise.race(), so production code may need a fallback.
const supportsPromiseAny = typeof Promise.any === 'function';
if (supportsPromiseAny) {
console.log('Promise.any() is supported in this browser.');
} else {
console.log('Promise.any() needs a polyfill or fallback.');
}