API trả danh sách nhóm, mỗi nhóm có nhiều thành viên. Bạn cần lấy id để đưa vào một thao tác tiếp theo nhưng map tạo ra mảng lồng. flatMap phù hợp nếu sản phẩm cần ghép các kết quả con thành một danh sách; map phù hợp nếu ranh giới nhóm vẫn có ý nghĩa.
Bài đối chiếu MDN ngày 06/11/2026 và chạy ví dụ với Node.js 24.18.1. Kiểm tra dùng dữ liệu cục bộ, không gọi API hoặc benchmark tốc độ. Các mảng đầu vào trong bài là mảng đặc, không sửa cấu trúc mảng trong callback.
So sánh theo hình dạng đầu ra
| Tiêu chí | map | flatMap |
|---|---|---|
| Callback trả về một mảng | Giữ mảng ấy làm một phần tử | Ghép các phần tử của mảng ấy |
| Số kết quả trên một đầu vào | Một giá trị | Có thể không có, một hoặc nhiều giá trị |
| Làm phẳng nhiều tầng | Không | Chỉ một cấp kết quả callback |
| Callback async | Nhận các Promise | Cũng nhận các Promise, không tự await |
| Dữ liệu gốc | Không tự sửa | Không tự sửa |
Cột cuối không bảo đảm object bên trong bất biến. Callback của bạn vẫn có thể sửa object đang được tham chiếu. Hai phương thức giúp tạo mảng kết quả, không tự sao chép sâu toàn bộ dữ liệu.
1. Từ nhóm thành danh sách id
Lưu đoạn sau thành file .cjs và chạy Node. Ví dụ đặt nhóm rỗng ở giữa để kiểm tra nó được giữ hay bỏ đi:
const assert = require('node:assert/strict');
const groups = [
{name: 'mobile', members: [{id: 1}, {id: 2}]},
{name: 'empty', members: []},
{name: 'backend', members: [{id: 3}]},
];
const groupedIds = groups.map(group => group.members.map(item => item.id));
const allIds = groups.flatMap(group => group.members.map(item => item.id));
assert.deepEqual(groupedIds, [[1, 2], [], [3]]);
assert.deepEqual(allIds, [1, 2, 3]);
assert.equal(groups.length, 3);
const oneLevel = [1, 2].flatMap(value => [[value, value * 10]]);
assert.deepEqual(oneLevel, [[1, 10], [2, 20]]);
console.log({groupedIds, allIds, oneLevel});
Ở allIds, mỗi callback trả một mảng id; flatMap ghép chúng thành kết quả chung. Ở oneLevel, callback trả một mảng chứa thêm mảng con, nên sau một lần làm phẳng vẫn còn mảng con. Không đọc chữ “flat” như cam kết mọi tầng đều được bỏ.
Nếu bước tiếp theo cần thông tin nhóm của từng id, việc ghép chỉ id đã làm mất ngữ cảnh đó. Có thể giữ map hoặc tạo object có cả groupName và memberId trước khi ghép. Hình dạng dữ liệu nên đi từ hợp đồng của nơi nhận, không đi từ mong muốn giảm số method.
2. Dùng mảng rỗng để loại kết quả
Một phần tử đầu vào có thể tạo không kết quả hoặc nhiều kết quả. Ví dụ độc lập sau tách các dòng chữ thành từ, bỏ dòng trắng:
const assert = require('node:assert/strict');
const lines = [' Dart Flutter ', '', 'Node SQLite'];
const words = lines.flatMap(line => {
const trimmed = line.trim();
return trimmed ? trimmed.split(/\s+/) : [];
});
assert.deepEqual(words, ['Dart', 'Flutter', 'Node', 'SQLite']);
assert.deepEqual([1, 2].flatMap(value => value === 1 ? [] : [value, value]), [2, 2]);
const promises = [1, 2].flatMap(async value => [value]);
assert.equal(promises.length, 2);
assert.ok(promises.every(value => value instanceof Promise));
console.log('words and async return shape verified');
Các Promise không trở thành mảng chỉ vì sau này chúng sẽ fulfilled bằng mảng. Muốn chạy callback async rồi ghép kết quả, cần một bước chờ theo chính sách xử lý lỗi của ứng dụng, sau đó mới làm phẳng những mảng đã có. flatMap không phải cơ chế điều phối tác vụ async.
Kiểm tra trước khi thay thế trong project
Đặt ca nhóm rỗng, một nhóm nhiều phần tử và nhiều nhóm liên tiếp. So sánh thứ tự id đầu ra với thứ tự dữ liệu đầu vào. Nếu dữ liệu API có members bằng null, quyết định reject hay chuẩn hóa tại biên; ví dụ không âm thầm coi mọi dữ liệu sai kiểu là mảng rỗng.
Nếu cần giữ object thành viên, thử sửa một object trong kết quả và xem nơi nào còn giữ cùng tham chiếu. Việc đó giúp nhóm hiểu rằng đổi map sang flatMap chỉ đổi cách ghép mảng, không tạo một bản sao object độc lập.
Tiêu chí chọn và giới hạn đánh giá
Dùng map khi mỗi đầu vào cần một kết quả và nhóm cần được bảo toàn. Dùng flatMap khi mỗi đầu vào sinh một mảng kết quả cần được ghép ngay một cấp. Với logic có nhiều nhánh, một vòng lặp rõ ràng có thể dễ bảo trì hơn callback dài.
Bài không kết luận phương thức nào nhanh hơn trên workload của bạn. Muốn chọn theo hiệu năng, đo dữ liệu, kích thước mảng và runtime thực tế sau khi đã xác nhận ngữ nghĩa tương đương. Trước hết hãy viết assertions về hình dạng đầu ra; đó là khác biệt có tác động trực tiếp đến đoạn code nhận kết quả.


