Giới Thiệu
Trong quá trình phát triển ứng dụng web, đặc biệt là trong thanh toán trực tuyến, việc sử dụng thời gian chính xác là vô cùng quan trọng. Một trong những vấn đề mà các lập trình viên thường gặp phải là việc sử dụng Date.now() mà không hiểu rõ cách thức hoạt động của nó. Bài viết này sẽ giải thích tại sao Date.now() phụ thuộc vào đồng hồ của người dùng và cách khắc phục vấn đề này để đảm bảo rằng các bộ đếm thời gian của bạn hoạt động chính xác.
Tóm Tắt
Date.now()sử dụng đồng hồ hệ thống của người dùng, không phải của máy chủ.- Nếu thời gian trên thiết bị của người dùng không chính xác, bộ đếm thời gian của bạn cũng sẽ sai lệch.
- Luôn xem máy chủ là nguồn thông tin chính xác cho các luồng nhạy cảm về thời gian.
Câu Chuyện Đằng Sau: Bộ Đếm Thời Gian Thanh Toán Sai
Khi tích hợp hệ thống thanh toán NuPay của Nubank, tôi đã xây dựng một bộ đếm thời gian đơn giản: 10 phút (600 giây) trước khi liên kết thanh toán hết hạn. Nghe có vẻ đơn giản, đúng không?
Tôi đã lấy thông tin đơn hàng, lấy dấu thời gian insertedAt, và so sánh với Date.now(). Nhưng bộ đếm của tôi liên tục hiển thị 11:50 phút thay vì 10:00.
Vấn Đề: Date.now() Sử Dụng Đồng Hồ Của Client
Sau khi kiểm tra lại phép toán của mình, tôi đã thử một điều bất thường: Tôi đã thay đổi đồng hồ hệ thống của mình một cách thủ công. Ngay lập tức, bộ đếm đã thay đổi. Đó là lúc tôi nhận ra:
Date.now()không dựa trên thời gian của máy chủ. Nó dựa vào đồng hồ hệ thống của người dùng.
Nếu thời gian hệ thống sai — ngay cả khi chỉ vài phút — bộ đếm của bạn sẽ bị lệch. Trong thanh toán, điều đó là một thảm họa.
Cách Tôi Khắc Phục Vấn Đề
-
Lưu Dấu Thời Gian Địa Phương Khi Thanh Toán
javascriptlocalStorage.setItem(`nupay_order_${orderId}`, Date.now().toString())Điều này hoạt động đáng tin cậy trên cùng một thiết bị.
-
Sử Dụng Trên Trang Xác Nhận
So sánh giá trị đã lưu vớiDate.now()để tính toán thời gian còn lại. -
Sử Dụng Dữ Liệu Từ Máy Chủ
Nếu khóa địa phương không tồn tại (ví dụ: người dùng chuyển thiết bị), hãy sử dụnginsertedAttừ backend.
Kiểm Tra Thực Tế Giữa Các Thiết Bị
Nếu một người dùng thanh toán trên máy tính xách tay nhưng xem đơn hàng trên điện thoại của họ thì sao?
- LocalStorage chỉ hoạt động trên cùng một thiết bị.
- Dấu Thời Gian Từ Máy Chủ sẽ bao quát các trường hợp giữa các thiết bị.
- Thực hành tốt nhất: hãy để backend trả về thời gian hiện tại cùng với
insertedAtđể bạn có thể tính toán tương đối với đồng hồ máy chủ.
Giám Sát Trạng Thái Đơn Hàng
Để giữ cho trạng thái đơn hàng luôn cập nhật, tôi đã sử dụng polling (mỗi 5 giây):
- Đơn giản hơn WebSockets/SSE
- Stateless, đáng tin cậy, dễ thử lại
- Hoàn hảo cho một quy trình ngắn hạn, quan trọng như thanh toán
javascript
async function pollOrderStatus(orderId, timeout, signal) {
const startTime = Date.now();
const interval = 5000;
while (true) {
// dừng polling nếu yêu cầu bị hủy hoặc vượt quá thời gian tối đa
if (signal.aborted || Date.now() - startTime > timeout) break;
const { data } = await client.query({
query: GetOrder,
variables: { orderId },
fetchPolicy: 'no-cache'
});
// Kiểm tra xem trạng thái đơn hàng đã thay đổi sang đã thanh toán/thất bại/đã hủy hay chưa
if (data?.order?.status !== 'pending') {
window.location.reload();
break;
}
await new Promise(r => setTimeout(r, interval));
}
}
Bài Học Chính: Đừng Tin Tưởng Vào Đồng Hồ Của Client
Date.now()chỉ chính xác như cài đặt hệ thống của người dùng.- Đồng hồ có thể lệch vài phút hoặc vài giờ.
- Việc sử dụng giữa các thiết bị làm tăng thêm vấn đề.
Luôn ưu tiên:
- Dấu thời gian từ máy chủ như nguồn thông tin chính xác
- Local storage như một trợ thủ trên cùng một thiết bị
- Các dung sai nhỏ nơi độ chính xác không quan trọng
Hình Dung Quy Trình
plaintext
Người Dùng Thanh Toán → Đơn Hàng Được Tạo (thời gian 10 phút) → Lưu dấu thời gian địa phương
↓
Trang Xác Nhận → Tính Toán Thời Gian Còn Lại → Hiển Thị Bộ Đếm
↓
Bắt Đầu Polling (5s) → Kiểm Tra Trạng Thái Đơn Hàng → Cập Nhật UI
↓
Thay Đổi Trạng Thái → Tải Lại Trang → Trạng Thái Cuối (Phản hồi thời gian thực cho người dùng)
Kết Luận
Hầu hết chúng ta coi Date.now() là điều hiển nhiên, nhưng trong các ứng dụng nhạy cảm về thời gian như thanh toán, nó có thể âm thầm phá hủy logic của bạn.
Lần tới khi bạn xây dựng một bộ đếm ngược, hãy nhớ:
Đồng hồ client không phải là chân lý. Máy chủ là nguồn thông tin chính xác của bạn.
Tài Liệu Tham Khảo
- MDN: Date.now()
- MDN: Performance.now()
Kết Nối Với Tôi
Tôi chia sẻ những bài học giải quyết vấn đề và gỡ lỗi trong thực tế:
GitHub | LinkedIn | Portfolio