Hai nguồn có thể trả cùng một loại dữ liệu. Nguồn đầu lỗi trước, nguồn sau vẫn trả được kết quả hợp lệ. Bạn muốn tiếp tục chờ nguồn sau hay kết thúc ngay theo lỗi đầu tiên? Câu trả lời quyết định chọn Promise.any hay Promise.race.
Bài đối chiếu MDN ngày 05/11/2026 và chạy ví dụ trên Node.js 24.18.1. Kiểm tra dùng promise do chương trình chủ động resolve hoặc reject, không gọi API thật, không dùng tốc độ mạng để suy luận. Bài so sánh ngữ nghĩa kết quả và cách tích hợp; không có benchmark hiệu năng hoặc bộ nhớ.
Cùng đầu vào, khác điều kiện kết thúc
settled nghĩa là promise đã fulfilled hoặc rejected. fulfilled chỉ nhánh thành công. race theo promise settled đầu tiên; any theo promise fulfilled đầu tiên. any chỉ reject khi mọi đầu vào đều reject.
| Tiêu chí | Promise.race | Promise.any |
|---|---|---|
| Nhánh lỗi đến trước | Kết thúc bằng lỗi ấy | Tiếp tục chờ khả năng thành công |
| Nhánh thành công đến trước | Trả giá trị ấy | Trả giá trị ấy |
| Tất cả đều lỗi | Theo lỗi settled đầu tiên | AggregateError chứa các lý do |
| Iterable rỗng | Giữ pending | Reject bằng AggregateError |
| Các tác vụ còn lại | Không tự hủy | Không tự hủy |
Đây là các phương thức chuẩn của Promise, không yêu cầu dependency riêng trong môi trường Node được dùng. Với runtime khác, kiểm tra khả năng hỗ trợ Promise.any và AggregateError trước khi đưa vào bundle.
1. Làm một nhánh lỗi rồi mới cho nhánh kia thành công
Lưu mỗi ví dụ thành file .cjs để chạy bằng Node. Đoạn đầu dùng một async function để không phụ thuộc top-level await:
const assert = require('node:assert/strict');
function deferred() {
let resolve, reject;
const promise = new Promise((yes, no) => {
resolve = yes;
reject = no;
});
return {promise, resolve, reject};
}
async function main() {
const first = deferred();
const second = deferred();
const inputs = [first.promise, second.promise];
const raced = Promise.race(inputs).then(
value => ({kind: 'ok', value}),
error => ({kind: 'error', message: error.message}),
);
const firstSuccess = Promise.any(inputs);
first.reject(new Error('source A failed'));
assert.deepEqual(await raced, {
kind: 'error', message: 'source A failed',
});
second.resolve('source B data');
assert.equal(await firstSuccess, 'source B data');
console.log('race rejected; any fulfilled');
}
main().catch(error => { console.error(error); process.exitCode = 1; });
Handler của race được gắn trước khi gây lỗi. Nhánh thứ hai được resolve sau khi đã xác nhận race reject, nên kết quả không phụ thuộc việc timer nào vô tình chạy nhanh hơn. any vẫn có thể trả dữ liệu từ nhánh thứ hai.
2. Tất cả lỗi: đọc đúng AggregateError
Ví dụ độc lập tiếp theo làm promise thứ hai reject trước, nhưng mảng errors vẫn theo thứ tự đầu vào:
const assert = require('node:assert/strict');
async function main() {
let failA, failB;
const a = new Promise((_, reject) => { failA = reject; });
const b = new Promise((_, reject) => { failB = reject; });
const result = Promise.any([a, b]).catch(error => error);
failB(new Error('B'));
failA(new Error('A'));
const error = await result;
assert.ok(error instanceof AggregateError);
assert.deepEqual(error.errors.map(item => item.message), ['A', 'B']);
const empty = await Promise.any([]).catch(reason => reason);
assert.ok(empty instanceof AggregateError);
assert.equal(empty.errors.length, 0);
console.log('all rejected and empty input verified');
}
main().catch(error => { console.error(error); process.exitCode = 1; });
Không dựa vào chuỗi message mặc định của AggregateError để viết logic ứng dụng. Cũng không await Promise.race([]) với kỳ vọng nhận undefined: promise ấy không tự hoàn tất.
Chọn theo hợp đồng của công việc
Dùng any khi một kết quả thành công từ các nguồn tương đương là đủ. “Thành công” phải được bạn định nghĩa trước khi đưa promise vào any. Ví dụ một lời gọi fetch fulfilled chưa tự chứng minh HTTP status hay payload hợp lệ; cần kiểm tra chúng và reject khi không đạt hợp đồng.
Dùng race khi kết quả settled đầu tiên có ý nghĩa, chẳng hạn phối hợp một tác vụ với một promise báo hết hạn. Tuy nhiên, race chỉ quyết định promise tổng hợp. Timer hoặc request còn lại vẫn cần cơ chế dọn dẹp riêng, và khả năng hủy phụ thuộc API nhận tín hiệu hủy.
Không dùng hai cách này để gửi lặp thao tác ghi chỉ vì muốn nhận nhanh hơn. Bài kiểm tra không chứng minh tính an toàn khi gọi nhiều nguồn tạo đơn hay thanh toán. Hãy bắt đầu từ tác vụ đọc độc lập, ghi log từng nguồn và kiểm tra rõ nhánh tất cả lỗi trước khi tích hợp.

