Timers Node.js: 'Máy Thời Gian' Cho Code Của Bạn - Creyt Dẫn Lối!
Nodejs

Timers Node.js: 'Máy Thời Gian' Cho Code Của Bạn - Creyt Dẫn Lối!

Author

Admin System

@root

Ngày xuất bản

19 Mar, 2026

Lượt xem

1 Lượt

"timers module"

Chào các "coder nhí" của Creyt! Hôm nay, chúng ta sẽ cùng nhau "hack" thời gian trong Node.js với một chủ đề cực kỳ quan trọng và thú vị: Timers Module. Nghe có vẻ phức tạp, nhưng tin Creyt đi, nó giống như việc bạn có một chiếc điều khiển từ xa để tua nhanh, tua chậm, hoặc đặt lịch cho các hành động của code vậy. Giống như việc bạn đặt báo thức để dậy học bài, hay hẹn giờ cho nồi cơm điện, code của chúng ta cũng cần được "lên lịch" đúng lúc, đúng chỗ.

Timers Là Gì Và Để Làm Gì?

Trong thế giới bất đồng bộ của Node.js, mọi thứ không chạy theo kiểu "từ trên xuống dưới" một cách tuyến tính như bạn nghĩ. Có những lúc bạn muốn một đoạn code chạy sau một khoảng thời gian nhất định, hoặc lặp đi lặp lại sau mỗi N giây, hoặc thậm chí là chạy ngay lập tức nhưng không làm nghẽn luồng chính. Đó chính là lúc các "timers" lên tiếng.

Các "timers" cơ bản mà chúng ta sẽ làm việc cùng là:

  1. setTimeout(callback, delay): "Đặt báo thức" cho code. Chạy callback sau delay mili giây. Chạy một lần duy nhất.
  2. setInterval(callback, delay): "Đặt báo thức lặp đi lặp lại". Chạy callback sau mỗi delay mili giây, cho đến khi bạn dừng nó lại.
  3. setImmediate(callback): "Ưu tiên chạy ngay sau I/O". Chạy callback ngay lập tức, nhưng sau các hoạt động I/O trong chu kỳ Event Loop hiện tại.
  4. process.nextTick(callback): "Ưu tiên cao nhất, chạy ngay lập tức". Chạy callback ngay sau khi tác vụ hiện tại hoàn thành, trước cả setImmediate và các phase khác của Event Loop.

Nghe có vẻ hơi "xoắn não" nhỉ? Đừng lo, Creyt sẽ giải thích từng cái một bằng ví dụ cụ thể.

1. setTimeout: Khi Bạn Muốn Code "Ngủ Đông" Một Chút

setTimeout giống như việc bạn đặt hẹn giờ cho lò vi sóng. "Này, sau 5 phút nữa thì làm nóng món này nhé!" Code của bạn sẽ chờ, và đúng lúc đó, nó sẽ thực thi. Nó hoàn hảo cho các tác vụ chỉ cần chạy một lần sau một khoảng thời gian.

Cú pháp:

const timeoutId = setTimeout(() => {
  console.log('Chào bạn, đây là tin nhắn sau 2 giây!');
}, 2000); // 2000 miligiây = 2 giây

// Bạn có thể hủy hẹn giờ này nếu đổi ý (giống như tắt lò vi sóng trước khi hết giờ)
// clearTimeout(timeoutId);

console.log('Tôi chạy trước, sau đó tin nhắn sẽ xuất hiện!');

Giải thích: timeoutId là một định danh duy nhất. Nếu bạn muốn hủy bỏ việc hẹn giờ trước khi nó kịp chạy, bạn dùng clearTimeout(timeoutId). Rất tiện lợi đúng không?

2. setInterval: Khi Bạn Muốn Code "Lặp Lại" Hành Động

setInterval giống như chiếc đồng hồ báo thức hàng ngày của bạn. "Cứ 7h sáng lại kêu một lần nhé!" Nó sẽ liên tục chạy một đoạn code sau mỗi khoảng thời gian nhất định, cho đến khi bạn "tắt báo thức" (dùng clearInterval). Tuyệt vời cho các tác vụ cần cập nhật liên tục hoặc kiểm tra định kỳ.

Cú pháp:

let count = 0;
const intervalId = setInterval(() => {
  console.log(`Đã ${++count} lần tôi đếm được sau mỗi 1 giây.`);
  if (count === 5) {
    clearInterval(intervalId); // Dừng đếm sau 5 lần
    console.log('Đã đủ 5 lần, dừng đếm!');
  }
}, 1000);

console.log('Bắt đầu đếm ngược...');

Giải thích: Tương tự setTimeout, intervalId là định danh để bạn có thể dùng clearInterval(intervalId) mà "tắt báo thức" khi không cần nữa. Mẹo nhỏ: Luôn nhớ clearInterval khi không dùng nữa để tránh rò rỉ bộ nhớ hoặc các tác vụ không cần thiết chạy mãi mãi!

3. setImmediate: "Ưu Tiên VIP Sau Giao Dịch"

setImmediate hơi đặc biệt một chút. Nó không quan tâm đến delay. Nó nói: "Này, hãy chạy tôi ngay sau khi Node.js hoàn thành các hoạt động I/O (ví dụ: đọc file, kết nối mạng) trong chu kỳ Event Loop hiện tại." Tưởng tượng bạn vừa hoàn tất một giao dịch ngân hàng, và ngân hàng muốn gửi cho bạn một tin nhắn xác nhận ngay sau đó, nhưng không phải là một phần của giao dịch chính. Đó là setImmediate.

Cú pháp:

console.log('Bắt đầu');

setImmediate(() => {
  console.log('Đây là tin nhắn từ setImmediate, chạy sau I/O.');
});

setTimeout(() => {
  console.log('Đây là tin nhắn từ setTimeout 0ms (có thể chạy sau setImmediate)');
}, 0);

// Giả lập một tác vụ I/O (ví dụ: đọc file)
require('fs').readFile(__filename, () => {
  console.log('I/O hoàn thành.');
  setImmediate(() => {
    console.log('setImmediate SAU I/O hoàn thành.');
  });
});

console.log('Kết thúc');

Giải thích: Trong hầu hết các trường hợp, setImmediate sẽ chạy trước setTimeout với delay là 0ms. Lý do nằm ở cách Event Loop của Node.js hoạt động. setImmediate được xử lý trong phase check, còn setTimeout (dù là 0ms) được xử lý trong phase timers. Phase check thường chạy sau phase timers nếu không có I/O nào xảy ra. Tuy nhiên, nếu có I/O, setImmediate sẽ được ưu tiên chạy ngay sau khi I/O hoàn thành.

Illustration

4. process.nextTick: "Ưu Tiên Tối Thượng, Ngay Lập Tức!"

process.nextTick là kẻ quyền lực nhất trong các timers. Nó không phải là một phần của chu kỳ Event Loop theo nghĩa đen, mà nó chạy ngay lập tức sau khi code hiện tại hoàn thành và trước khi Event Loop chuyển sang phase tiếp theo. Giống như bạn đang thuyết trình, và có một ý nghĩ lóe lên trong đầu bạn, bạn phải nói ra ngay lập tức trước khi chuyển sang slide tiếp theo. Nó cực kỳ hữu ích để đảm bảo một hành động nào đó diễn ra ngay sau khi code hiện tại kết thúc, nhưng không chặn code hiện tại.

Cú pháp:

console.log('1. Bắt đầu script');

process.nextTick(() => {
  console.log('2. Đây là process.nextTick, chạy ngay sau code hiện tại.');
});

setImmediate(() => {
  console.log('4. Đây là setImmediate, chạy sau nextTick và I/O.');
});

setTimeout(() => {
  console.log('5. Đây là setTimeout 0ms, chạy sau cùng (thường là vậy).');
}, 0);

console.log('3. Kết thúc script (nhưng nextTick sẽ chạy trước setImmediate/setTimeout).');

Thứ tự chạy (thường thấy): 1 -> 3 -> 2 -> 4 -> 5.

Giải thích: process.nextTick có độ ưu tiên cao nhất, nó chạy ngay sau khi stack cuộc gọi hiện tại rỗng. Điều này có nghĩa là nó sẽ chạy trước bất kỳ setImmediate, setTimeout hay các I/O callback nào khác.

Module timers Nâng Cao (Node.js)

Ngoài các hàm global trên, Node.js còn cung cấp một module timers để bạn có thể require('timers') hoặc require('timers/promises') để dùng các phiên bản dựa trên Promise của setTimeoutsetInterval, hoặc các hàm như ref()/unref() để điều khiển cách timer ảnh hưởng đến việc thoát của tiến trình Node.js. Ví dụ:

const { setTimeout: promiseSetTimeout } = require('timers/promises');

async function delayedGreeting() {
  console.log('Đang chờ...');
  await promiseSetTimeout(3000); // Chờ 3 giây
  console.log('Xin chào sau 3 giây với Promise!');
}

delayedGreeting();

ref()unref() cho phép bạn kiểm soát xem một timer có giữ cho tiến trình Node.js không thoát hay không. Mặc định, setTimeoutsetInterval sẽ giữ cho tiến trình không thoát. unref() sẽ cho phép tiến trình thoát nếu đó là timer duy nhất còn lại.

Mẹo Vặt (Best Practices) Từ Giảng Viên Creyt

  1. Luôn clearTimeoutclearInterval: Nếu bạn không cần timer nữa, hãy hủy bỏ nó! Điều này giúp tránh rò rỉ bộ nhớ và các tác vụ không mong muốn. Giống như bạn tắt chuông báo thức khi đã dậy vậy.
  2. Hiểu rõ Event Loop: Đây là trái tim của Node.js. Việc hiểu process.nextTick, setImmediate, và setTimeout tương tác với Event Loop như thế nào sẽ giúp bạn debug và viết code bất đồng bộ hiệu quả hơn.
  3. Không chặn Event Loop: Các callback trong timer phải là non-blocking. Nếu bạn đặt một tác vụ tính toán nặng vào setTimeout, nó sẽ chặn Event Loop và làm chậm toàn bộ ứng dụng của bạn. Hãy dùng Worker Threads cho các tác vụ nặng.
  4. setImmediate vs setTimeout(0): setImmediate thường được ưu tiên hơn setTimeout(0) khi có I/O. Hãy dùng setImmediate khi bạn muốn defer một tác vụ non-blocking đến cuối phase I/O hiện tại.
  5. process.nextTick cho deferral tức thì: Dùng process.nextTick khi bạn cần chạy một tác vụ ngay sau code hiện tại, nhưng trước bất kỳ I/O hay timer nào khác. Cẩn thận đừng lạm dụng vì nó có thể làm Event Loop "đói" I/O.

Ví Dụ Thực Tế Các Ứng Dụng/Website Đã Ứng Dụng

  • Ứng dụng Chat/Tin nhắn thời gian thực (ví dụ: Zalo, Slack): Dùng setInterval để gửi "heartbeat" (tín hiệu sống) đến server để duy trì kết nối hoặc kiểm tra trạng thái online của người dùng. Hoặc setTimeout để trì hoãn gửi thông báo nếu người dùng đang gõ.
  • Hệ thống lên lịch tác vụ (ví dụ: Cron jobs): Mặc dù Node.js có các thư viện chuyên dụng như node-cron, nhưng về cơ bản, chúng cũng dựa trên setInterval hoặc setTimeout để kích hoạt các tác vụ định kỳ (gửi email báo cáo hàng ngày, dọn dẹp database hàng tuần).
  • Game Servers (ví dụ: Game online đơn giản): setInterval được dùng để cập nhật trạng thái game (vị trí người chơi, điểm số, vật phẩm) sau mỗi khung hình hoặc sau mỗi khoảng thời gian nhất định.
  • API Rate Limiting: setTimeout có thể dùng để reset số lượng request mà một người dùng có thể thực hiện trong một khoảng thời gian nhất định.
  • Animations và UI updates (trong Electron/React Native): Mặc dù chủ yếu là frontend, nhưng các nguyên tắc về setTimeout/setInterval để tạo hiệu ứng hoặc cập nhật UI định kỳ cũng tương tự.

Thử Nghiệm Đã Từng và Hướng Dẫn Nên Dùng Cho Case Nào

Giảng viên Creyt đã từng "đau đầu" với việc debug thứ tự chạy của setTimeout(0)setImmediate khi mới học Node.js. Nó giống như một trò chơi "oẳn tù tì" của Event Loop vậy. Qua nhiều lần thử nghiệm, Creyt đúc kết ra rằng:

  • Dùng setTimeout khi: Bạn muốn trì hoãn một tác vụ trong một khoảng thời gian cụ thể (ví dụ: "gửi email nhắc nhở sau 1 tiếng", "hiển thị thông báo lỗi sau 3 giây").
  • Dùng setInterval khi: Bạn cần một tác vụ lặp đi lặp lại một cách định kỳ (ví dụ: "cập nhật tỷ giá chứng khoán mỗi 5 phút", "kiểm tra tình trạng server mỗi 10 giây"). Nhớ clearInterval!
  • Dùng setImmediate khi: Bạn muốn defer một tác vụ non-blocking đến cuối phase I/O hiện tại. Thường dùng trong các module có xử lý I/O để trả về kết quả đồng bộ nhưng xử lý tiếp theo bất đồng bộ.
  • Dùng process.nextTick khi: Bạn muốn đảm bảo một tác vụ chạy ngay sau khi hàm hiện tại hoàn thành, trước bất kỳ I/O hay timer nào khác. Rất hữu ích khi bạn muốn xử lý một callback hoặc emit một sự kiện ngay lập tức để hoàn tất một hành động đang diễn ra, nhưng vẫn giữ cho Event Loop không bị chặn quá lâu.

Nhớ nhé các bạn, việc làm chủ các timers này không chỉ giúp code của chúng ta chạy đúng lúc, đúng chỗ mà còn giúp ứng dụng của chúng ta linh hoạt và hiệu quả hơn rất nhiều. Hãy thực hành và thử nghiệm nhiều vào để "cảm" được nó nhé! Hẹn gặp lại trong buổi học tiếp theo!

Thuộc Series: Nodejs

Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!

#tech #cyberpunk #laravel
Chỉnh sửa bài viết

Bình luận (0)

Vui lòng Đăng Nhập để Bình luận

Hỗ trợ Markdown cơ bản
Nguyễn Văn A
1 ngày trước

Tính năng này đỉnh quá ad ơi, chờ mãi mới thấy một blog Tiếng Việt có UI/UX xịn như vầy!