Lập trình · 19/10/2026

JavaScript flatMap: biến một phần tử thành không, một hoặc nhiều kết quả

Một đơn hàng có nhiều dòng hàng, còn màn hình báo cáo cần một danh sách dòng phẳng. map giữ mỗi đơn thành một kết quả; flatMap phù hợp khi mỗi đầu vào có thể tạo ra không, một hoặc nhiều phần tử đầu ra.

JavaScript flatMap: biến một phần tử thành không, một hoặc nhiều kết quả

Một đơn hàng có nhiều dòng hàng, còn màn hình báo cáo cần một danh sách dòng phẳng. map giữ mỗi đơn thành một kết quả; flatMap phù hợp khi mỗi đầu vào có thể tạo ra không, một hoặc nhiều phần tử đầu ra.

Điểm cần kiểm soát là hình dạng kết quả callback. Trả mảng rỗng để bỏ qua và trả mảng các phần tử để mở rộng là hai thao tác rõ ràng; vô tình trả undefined lại có ý nghĩa khác.

1. Tách dòng hàng nhưng giữ thông tin nguồn

const orders = [
  { id: 'A', items: ['keyboard', 'mouse'] },
  { id: 'B', items: [] },
  { id: 'C', items: ['screen'] }
];
const rows = orders.flatMap(order =>
  order.items.map(item => ({ orderId: order.id, item }))
);

console.log(rows.map(row => row.orderId + ':' + row.item).join('|'));
// A:keyboard|A:mouse|C:screen

Đơn B không tạo dòng nào vì items rỗng. Mỗi dòng mới giữ orderId để còn truy ngược đơn nguồn. Khi biến đổi dữ liệu cho xuất báo cáo, đừng bỏ mất khóa liên kết chỉ vì danh sách phẳng dễ hiển thị hơn.

Ví dụ giả định đầu vào đã được kiểm tra: orders là mảng, mỗi đơn có id và items đúng schema. flatMap không tự xác nhận schema; nếu API có thể trả items là null hoặc sai kiểu, cần xử lý ở biên dữ liệu theo chính sách của ứng dụng.

2. Dùng mảng rỗng để bỏ qua

const values = [2, -1, 3];
const expanded = values.flatMap(value =>
  value < 0 ? [] : [value, value * 10]
);
const accidental = values.flatMap(value => {
  if (value >= 0) return [value];
});

console.log(JSON.stringify(expanded));   // [2,20,3,30]
console.log(accidental.length);         // 3
console.log(accidental[1] === undefined); // true

Callback không return ở nhánh âm sẽ trả undefined, và giá trị đó vẫn được thêm vào kết quả. Nó không tương đương []. Nếu muốn giữ một phần tử thì trả [value]; muốn bỏ thì trả []; muốn tách thì trả nhiều phần tử trong mảng.

Tránh dùng việc thiếu return như một cách lọc ngầm. Khi review, kiểm tra mọi nhánh callback để thấy rõ số lượng đầu ra mà mỗi nhánh tạo ra.

3. Chỉ làm phẳng một tầng

const nested = [1, 2].flatMap(value => [[value, value * 10]]);
const text = ['ab', 'cd'].flatMap(value => value);

console.log(JSON.stringify(nested)); // [[1,10],[2,20]]
console.log(JSON.stringify(text));   // ["ab","cd"]

Mảng ngoài do callback trả về được nối vào kết quả, còn mảng bên trong vẫn còn nguyên. Một chuỗi trả trực tiếp cũng không bị tách thành từng ký tự: flatMap chỉ trải phần tử khi giá trị trả về là mảng.

Nếu đầu ra cần độ sâu khác, hãy đặt rõ schema mong muốn. Gọi flat nhiều lần mà chưa hiểu dữ liệu có thể xóa mất ranh giới nhóm cần thiết cho nghiệp vụ.

4. Mảng mới không đồng nghĩa object độc lập

const item = { label: 'Draft' };
const source = [item];
const result = source.flatMap(value => [value]);
result[0].label = 'Ready';

console.log(result !== source);       // true
console.log(result[0] === source[0]); // true
console.log(source[0].label);         // Ready

Phương thức tạo mảng kết quả mới nhưng không tự sao chép sâu object được trả về. Nếu muốn sửa dữ liệu sau biến đổi mà không chạm nguồn, cần tạo object mới ở đúng tầng cần sửa. Callback cũng có thể tự sửa đầu vào, nên việc dùng flatMap không chứng minh toàn bộ thao tác là thuần.

5. Callback async trả Promise, chưa trả mảng

const pending = [1, 2].flatMap(async value => [value, value * 10]);
console.log(pending[0] instanceof Promise); // true

const batches = await Promise.all(
  [1, 2].map(async value => [value, value * 10])
);
console.log(JSON.stringify(batches.flat())); // [1,10,2,20]

Đoạn này chạy trong async function hoặc module có hỗ trợ top-level await. flatMap không đợi Promise để biết mảng bên trong; vì vậy ví dụ đợi các kết quả bằng Promise.all rồi làm phẳng.

Đây là phép tính local, không gọi mạng. Khi thay bằng request thật, cần chính sách lỗi và giới hạn đồng thời riêng. Promise.all không tự giới hạn số tác vụ hoặc hủy những tác vụ khác khi một tác vụ thất bại.

6. Kiểm thử hình dạng và số lượng đầu ra

  • Đầu vào rỗng tạo kết quả rỗng.
  • Có ca bỏ một phần tử, giữ một phần tử và mở rộng nhiều phần tử.
  • Không có undefined do nhánh quên return.
  • Giữ khóa nguồn và thứ tự nhóm khi tạo dòng báo cáo.
  • Kiểm tra tham chiếu object nếu bên gọi sẽ sửa kết quả.
  • Phân biệt mảng có vị trí trống với phần tử undefined; flatMap bỏ qua vị trí trống của mảng nguồn.
  • Đo dữ liệu thực tế trước khi kết luận flatMap nhanh hơn vòng lặp.

Các đoạn code đã chạy bằng Node.js local, gồm cả ví dụ async. Chưa đo hiệu năng hoặc kiểm thử tích hợp API sản phẩm. Tài liệu đối chiếu: Array.prototype.flatMap.

Chọn flatMap khi quy tắc không–một–nhiều làm ý định biến đổi dễ đọc. Khi cần trạng thái phức tạp hoặc dừng sớm, một vòng lặp rõ ràng có thể phù hợp hơn.

Thảo luận

Bình luận 0

Đăng nhập để bình luận

Bạn cần có tài khoản để tham gia thảo luận và trả lời độc giả khác.

Đăng nhậpĐăng ký

Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.