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

JavaScript every và some: vì sao danh sách rỗng vẫn có thể vượt qua kiểm tra?

Màn hình chưa có mục nào nhưng nhãn “Tất cả đã hoàn thành” vẫn xuất hiện. Một nguyên nhân là ứng dụng dùng every để kiểm tra danh sách mà chưa xác định danh sách rỗng có ý nghĩa gì. Phương thức có hành vi nhất quán; điều còn thiếu thường là quy tắc sản phẩm.

JavaScript every và some: vì sao danh sách rỗng vẫn có thể vượt qua kiểm tra?

Màn hình chưa có mục nào nhưng nhãn “Tất cả đã hoàn thành” vẫn xuất hiện. Một nguyên nhân là ứng dụng dùng every để kiểm tra danh sách mà chưa xác định danh sách rỗng có ý nghĩa gì. Phương thức có hành vi nhất quán; điều còn thiếu thường là quy tắc sản phẩm.

Bài viết dùng các kiểm tra đồng bộ trong bộ nhớ và một ví dụ async local. Mục tiêu là phân biệt “mọi phần tử đạt”, “có ít nhất một phần tử đạt” và “phải có dữ liệu để kiểm tra”.

1. every và some trả kết quả khác nhau với mảng rỗng

const tasks = [];
console.log(tasks.every(task => task.done === true)); // true
console.log(tasks.some(task => task.done === true));  // false

every không tìm thấy phần tử nào vi phạm nên trả true. some không tìm thấy phần tử nào đạt nên trả false. Với mảng rỗng, callback của cả hai đều không chạy.

Nếu màn hình yêu cầu có ít nhất một tác vụ và tất cả đều hoàn thành, cần thêm điều kiện số lượng. Đồng thời phân biệt dữ liệu chưa tải, tải lỗi và một danh sách rỗng hợp lệ; không biến mọi trạng thái thành [] rồi suy ra công việc đã xong.

2. Viết rõ quy tắc có dữ liệu

function allTasksDone(tasks) {
  if (!Array.isArray(tasks)) {
    throw new TypeError('Expected a task array');
  }
  return tasks.length > 0 && Array.from(tasks).every(task =>
    task !== null && typeof task === 'object' &&
    !Array.isArray(task) && task.done === true
  );
}

console.log(allTasksDone([]));                         // false
console.log(allTasksDone([{ done: true }]));           // true
console.log(allTasksDone([{ done: true }, { done: false }])); // false

Hàm minh họa chấp nhận mảng tác vụ, yêu cầu không rỗng và chỉ coi done bằng boolean true là hoàn thành. Chuỗi "true" hoặc số 1 không đạt. Array.from biến vị trí trống thông thường thành undefined để predicate không bỏ qua dữ liệu thiếu.

Đây là kiểm tra trạng thái, không phải schema validator đầy đủ: hàm không kiểm tra ID, tính duy nhất hay trường nghiệp vụ khác. Nó dành cho mảng dữ liệu thông thường, không phải cơ chế bảo vệ trước mọi object tùy biến hoặc Proxy.

3. Vị trí trống khác phần tử undefined

const holes = new Array(2);
const explicit = [undefined, undefined];
let calls = 0;

console.log(holes.every(() => { calls += 1; return false; })); // true
console.log(calls); // 0
console.log(explicit.every(value => value !== undefined)); // false

Trong ví dụ không có thuộc tính số kế thừa, every bỏ qua các vị trí trống của holes; nó không gọi callback hai lần với undefined. Với explicit, hai phần tử thực sự tồn tại nên phép kiểm tra nhìn thấy undefined.

Không dùng new Array(n).every(...) để chứng minh đã xử lý đủ n bản ghi. Nếu dữ liệu có thể là mảng thưa, hãy xác định rõ chấp nhận, từ chối hay chuyển đổi nó trước bước kiểm tra.

4. Dừng sớm là hành vi cần tính đến

const visited = [];
const found = [2, 7, 9].some(value => {
  visited.push(value);
  return value > 5;
});

console.log(found);             // true
console.log(visited.join(',')); // 2,7

some dừng khi gặp kết quả truthy đầu tiên; every dừng khi gặp falsy đầu tiên. Đừng đặt thao tác bắt buộc phải chạy cho tất cả phần tử, như ghi nhận mọi lỗi nhập liệu, bên trong predicate dừng sớm.

Nếu cần thu thập toàn bộ lỗi để hiển thị, dùng một lượt duyệt tạo danh sách lỗi rồi quyết định trạng thái tổng. Callback cũng không nên sửa mảng đang kiểm tra vì điều đó làm kết quả khó đọc và khó tái hiện.

5. Async predicate không được tự chờ

const wrong = [1, 2].every(async value => value < 0);
console.log(wrong); // true

const checks = await Promise.all(
  [1, 2].map(async value => value < 0)
);
console.log(checks.every(value => value === true)); // false

Chạy đoạn này trong async function hoặc module hỗ trợ top-level await. Async callback trả Promise, là object truthy, nên every không dùng boolean được resolve về sau. Viết await trước toàn bộ lời gọi every cũng không sửa được lỗi này.

Ví dụ đợi các boolean rồi mới kiểm tra. Với request thật, cần xử lý rejection, timeout và giới hạn đồng thời theo nhu cầu. Promise.all bắt đầu từ các tác vụ đã tạo trong map; nó không biến quá trình thành kiểm tra tuần tự dừng sớm. Nếu cần dừng sau kết quả đầu tiên, cân nhắc vòng lặp await với quy tắc rõ ràng.

6. Các ca cần đưa vào kiểm thử

  • Không có dữ liệu, có một phần tử và có nhiều phần tử.
  • Tất cả đạt, tất cả không đạt và kết quả trộn.
  • Giá trị true khác chuỗi "true", số 1 và trường bị thiếu.
  • Mảng có vị trí trống khác mảng chứa undefined.
  • Kiểm tra số lần callback chạy khi có thể dừng sớm.
  • Kiểm tra Promise resolve false và Promise reject theo chính sách async.

Các đoạn code đã chạy bằng Node.js local, chưa kiểm thử giao diện hay request mạng. Tài liệu đối chiếu: Array.prototype.every và Array.prototype.some.

Hãy đặt tên điều kiện theo ý nghĩa sản phẩm rồi chọn phương thức phù hợp. “Tất cả đạt” và “có dữ liệu, tất cả đạt” là hai yêu cầu cần được diễn đạt riêng.

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.