Trong phỏng vấn, người hỏi đưa một Map làm cache và hỏi bạn có thể dùng get để biết khóa đã được tra cứu chưa. Câu trả lời phụ thuộc vào hợp đồng giá trị. Nếu cache được phép lưu undefined, cùng một kết quả từ get có thể đại diện cho hai trạng thái khác nhau.
Hãy tưởng tượng bộ tra cứu nhãn hiển thị. Một mã đã được kiểm tra nhưng không có nhãn có thể được lưu với undefined để tránh tra lại. Một mã chưa bao giờ được kiểm tra cũng trả undefined khi đọc cache. Dùng cùng một nhánh cho hai trường hợp sẽ khiến cache không giảm được lượt tra cứu thiếu nhãn.
Tách câu hỏi về khóa và giá trị
Theo ECMAScript, get trả giá trị đã lưu nếu có khóa, còn nếu không có khóa thì trả undefined. has trả boolean về sự hiện diện. Bạn cần has khi ứng dụng cho phép giá trị lưu trùng với giá trị biểu thị không tìm thấy của get.
const assert = require('node:assert/strict');
const labels = new Map([['known-empty', undefined], ['zero', 0]]);
assert.equal(labels.get('known-empty'), undefined);
assert.equal(labels.get('never-looked-up'), undefined);
assert.equal(labels.has('known-empty'), true);
assert.equal(labels.has('never-looked-up'), false);
assert.equal(labels.has('zero'), true);
Các assertion chạy trên Node 24.18.1. Chúng kiểm tra cả trường hợp giá trị 0 vì điều kiện dựa vào truthiness cũng có thể bỏ qua một giá trị hợp lệ. Việc 0 hay undefined có hợp lệ trong cache thật là quyết định của ứng dụng, không phải yêu cầu của Map.
Thử bằng số lượt gọi
const assert = require('node:assert/strict');
const cache = new Map();
let lookups = 0;
function lookupLabel(key) {
if (cache.has(key)) return cache.get(key);
lookups++;
const label = key === 'A' ? 'Alpha' : undefined;
cache.set(key, label);
return label;
}
assert.equal(lookupLabel('X'), undefined);
assert.equal(lookupLabel('X'), undefined);
assert.equal(lookups, 1);
assert.equal(lookupLabel('A'), 'Alpha');
assert.equal(lookupLabel('A'), 'Alpha');
assert.equal(lookups, 2);
cache.delete('X');
assert.equal(lookupLabel('X'), undefined);
assert.equal(lookups, 3);
Ở đây lookup chỉ là một nhánh trong RAM. Bộ đếm đo số lần đi qua nhánh cache miss, không đo độ trễ hay số lần gọi API thực. Xóa khóa cho phép tra cứu lại; đó là hành vi khác với cập nhật giá trị của khóa thành undefined.
Khi nào hợp đồng cần rõ hơn?
Nếu không cho phép undefined trong giá trị lưu, việc dùng get và so với undefined có thể đủ. Hãy ghi điều kiện đó trong kiểu hoặc hàm ghi cache để người sửa code sau này không vô tình mở rộng tập giá trị mà quên cập nhật phép đọc.
Một phương án khác là lưu object thể hiện trạng thái, chẳng hạn bản ghi có nhãn hoặc bản ghi đã kiểm tra nhưng chưa có nhãn. Nó dài hơn một chút nhưng có chỗ để ghi thời điểm kiểm tra và nguồn của kết quả. Với cache dùng qua nhiều module, khả năng đọc trạng thái có thể đáng giá hơn một giá trị đặc biệt ngầm định.
Đừng lưu kết quả thiếu mãi nếu dữ liệu nguồn có thể đổi. Bài này chưa có TTL, tải đồng thời hay xử lý lỗi mạng. Nếu đem ví dụ vào tác vụ bất đồng bộ, cần quyết định riêng cách phân biệt lỗi tạm thời với kết quả đã tra cứu và không tồn tại.
Kết luận
Khi get trả undefined, hãy hỏi liệu undefined có được lưu hợp lệ không. Nếu có, dùng has để xác định khóa đã hiện diện, rồi đọc giá trị theo hợp đồng của ứng dụng.


