Đôi khi có một số hành chúng chúng ta không muốn thực hiện ngay lập tức, mà muốn chúng sẽ được thực hiện ở một khoảng thời gian nhất định. Ví dụ sau 10 giây kể từ khi người dùng truy cập trang web chẳng hạn. Để làm được điều này thì JavaScript có hàm hỗ trợ chúng ta làm điều đó. Trong bài viết này tôi sẽ giới thiệu về setTimeout() và setInterval() trong JavaScript. Hãy cùng nhau tìm hiểu nhé.
-
setTimeout
cho phép chúng ta chạy một function một lần sau khoảng thời gian nhất định. -
setInterval
cho phép chúng ta chạy một function lặp đi lặp lại, bắt đầu sau một khoảng thời gian, sau đó lặp lại liên tục trong khoảng thời gian đó.
Các phương thức này không phải là một phần của đặc tả JavaScript. Nhưng hầu hết các môi trường đều có bộ lên lịch nội bộ và cung cấp các hàm này. Đặc biệt, chúng được hỗ trợ trên tất cả các trình duyệt và Node.js.
Hàm setTimeout trong JavaScript
Cú pháp:
1 |
let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...) |
Trong đó:
func|code
Một function hoặc một chuỗi code để thực thi. Thông thường, đó là một function. Vì lý do lịch sử, một chuỗi code cũng có thể được truyền vào, nhưng điều đó không được khuyến nghị.
delay
Độ trễ trước khi chạy, tính bằng mili giây (1000 ms = 1 giây), theo mặc định là 0.
arg1
, arg2
…
Đối số cho hàm (không được hỗ trợ trong IE9-)
Ví dụ: đoạn code này gọi function sayHi ()
sau một giây:
1 2 3 4 5 |
function sayHi() { alert('Hello'); } setTimeout(sayHi, 1000); |
Với các đối số:
1 2 3 4 5 |
function sayHi(phrase, who) { alert( phrase + ', ' + who ); } setTimeout(sayHi, 1000, "Hello", "John"); // Hello, John |
Nếu đối số đầu tiên là một chuỗi, thì JavaScript sẽ tạo một hàm từ nó. Vì vậy, điều này cũng sẽ hoạt động:
1 |
setTimeout("alert('Hello')", 1000); |
Nhưng sử dụng chuỗi không được khuyến khích, hãy sử dụng các hàm mũi tên thay vì sử dụng chuỗi, như sau:
1 |
setTimeout(() => alert('Hello'), 1000); |
Xem thêm: Các loại function trong JavaScript.
Lưu ý
Các bạn mới làm quen đôi khi mắc lỗi khi thêm dấu ngoặc ()
sau hàm:
1 2 |
// sai! setTimeout(sayHi(), 1000); |
Điều đó không hoạt động, vì setTimeout
mong đợi một tham chiếu đến một hàm. Và ở đây sayHi()
chạy hàm và kết quả thực thi của nó được chuyển đến setTimeout
. Trong trường hợp này, kết quả của sayHi()
là không xác định (hàm không trả về gì), vì vậy không có gì được lên lịch.
Hủy lên lịch với clearTimeout
Lệnh gọi setTimeout
trả về timerId “định danh” mà chúng ta có thể sử dụng để hủy thực thi. Cú pháp hủy:
1 2 |
let timerId = setTimeout(...); clearTimeout(timerId); |
Trong đoạn mã dưới đây, tôi lập lịch cho chức năng và sau đó hủy bỏ nó. Kết quả là không có gì xảy ra:
1 2 3 4 5 |
let timerId = setTimeout(() => alert("never happens"), 1000); alert(timerId); // định danh timerId clearTimeout(timerId); alert(timerId); |
Như chúng ta có thể thấy từ đầu ra của hàm alert()
, trong trình duyệt, mã định danh là một số. Trong các môi trường khác, đây có thể là một cái gì đó khác. Ví dụ, Node.js trả về một đối tượng bộ đếm thời gian với các phương thức bổ sung.
Hàm setInterval trong JavaScript
Phương thức setInterval
có cùng cú pháp như setTimeout
:
1 |
let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...) |
Tất cả các đối số đều có cùng ý nghĩa. Nhưng không giống như setTimeout
, nó chạy chức năng không chỉ một lần mà lặp đi lặp lại sau một khoảng thời gian nhất định. Đây cũng là điểm khác nhau giữa setTimeout() và setInterval() trong JavaScript.
Để dừng các cuộc gọi tiếp theo, chúng ta nên sử dụng hàm clearInterval (timerId)
.
Ví dụ sau sẽ hiển thị thông báo sau mỗi 2 giây. Sau 5 giây, đầu ra bị dừng:
1 2 3 4 5 |
// Hiển thị thông báo 2 giây một lần let timerId = setInterval(() => alert('tick'), 2000); // Sau 5 giây thì dừng setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000); |
Thời gian tiếp tục trong khi cảnh báo được hiển thị
Trong hầu hết các trình duyệt, bao gồm cả Chrome và Firefox, bộ đếm thời gian bên trong sẽ tiếp tục trong khi hiển thị alert/confirm/prompt
.
Vì vậy, nếu bạn chạy mã ở trên và không loại bỏ cửa sổ cảnh báo trong một thời gian, thì cảnh báo tiếp theo sẽ được hiển thị ngay lập tức khi bạn thực hiện. Khoảng thời gian thực tế giữa các cảnh báo sẽ ngắn hơn 2 giây.
Hàm setTimeout lồng nhau
Có hai cách để thực hiện một cái gì đó lặp đi lặp lại trong một khoảng thời gian. Một là sử dụng hàm setInterval()
, hai là sử dụng hàm setTimeout()
lồng nhau. Giống như sau:
1 2 3 4 5 6 7 8 |
/** Thay vì sử dụng setInterval(): let timerId = setInterval(() => alert('tick'), 2000); */ // Ta có thể sử dụng setTimeout() lồng nhau let timerId = setTimeout(function tick() { alert('tick'); timerId = setTimeout(tick, 2000); // Được gọi sau khi kết thúc cuộc gọi hiện tại }, 2000); |
Hàm setTimeout
ở trên lên lịch cho cuộc gọi tiếp theo ngay khi kết thúc cuộc gọi hiện tại .
Giữa setTimeout() và setInterval() trong JavaScript, thì hàm setTimout() sử dụng linh hoạt hơn setInterval(). Bởi vì cuộc gọi tiếp theo trong setTimeout
có thể được lên lịch tùy thuộc vào kết quả của cuộc gọi hiện tại.
Ví dụ: chúng ta cần viết một dịch vụ gửi yêu cầu đến máy chủ cứ sau 5 giây yêu cầu dữ liệu, nhưng trong trường hợp máy chủ quá tải, nó nên tăng khoảng thời gian lên 10, 20, 40 giây…
1 2 3 4 5 6 7 8 9 10 11 12 13 |
let delay = 5000; let timerId = setTimeout(function request() { ...send request... if (yêu cầu không thành công do quá tải máy chủ) { // tăng interval cho lần chạy tiếp theo delay *= 2; } timerId = setTimeout(request, delay); }, delay); |
Và nếu các chức năng mà chúng ta đang lên lịch sử dụng CPU, thì chúng ta có thể đo thời gian thực hiện và lên kế hoạch cho cuộc gọi tiếp theo sớm hay muộn.
setTimeout
lồng nhau cho phép đặt độ trễ giữa các lần thực thi chính xác hơn setInterval
.
Hãy so sánh hai đoạn mã sau. Cái đầu tiên sử dụng setInterval
:
1 2 3 4 |
let i = 1; setInterval(function() { func(i++); }, 100); |
Cái thứ hai sử dụng setTimeout
lồng nhau:
1 2 3 4 5 |
let i = 1; setTimeout(function run() { func(i++); setTimeout(run, 100); }, 100); |
Đối với setInterval
, bộ lập lịch nội bộ sẽ chạy func (i ++)
sau mỗi 100ms:

Bạn có để ý không?
Độ trễ thực sự giữa các cuộc gọi func()
cho setInterval
ít hơn trong mã!
Điều đó là bình thường, vì thời gian thực thi của hàm func()
“tiêu tốn” một phần của khoảng thời gian. Có thể quá trình thực thi của hàm func()
hóa ra lâu hơn chúng ta mong đợi và mất hơn 100 mili giây.
Trong trường hợp này, nó sẽ đợi hàm func()
hoàn thành, sau đó kiểm tra bộ lập lịch và nếu hết thời gian, nó sẽ chạy lại ngay lập tức.
Và đây là hình ảnh cho setTimeout
lồng nhau:

setTimeout lồng nhau sẽ đảm bảo độ trễ cố định (ở đây là 100ms). Đó là vì một cuộc gọi mới được lên kế hoạch vào cuối cuộc gọi trước đó.
Điều gì sẽ xảy ra khi ta set độ trễ của setTimeout là 0?
Có một trường hợp sử dụng đặc biệt: setTimeout (func, 0)
hoặc chỉ setTimeout (func)
.
Bộ lập lịch sẽ gọi hàm func()
càng sớm càng tốt. Nhưng bộ lập lịch sẽ chỉ gọi nó sau khi tập lệnh hiện đang thực thi hoàn tất.
Vì vậy, hàm được lên lịch sẽ chạy “ngay sau” tập lệnh hiện tại.
Ví dụ: kết quả đầu ra là “Hello”, sau đó là “World”:
1 2 3 |
setTimeout(() => alert("World")); alert("Hello"); |
Dòng đầu tiên “đưa cuộc gọi vào lịch sau 0ms”. Nhưng trình lập lịch sẽ chỉ “kiểm tra lịch” sau khi tập lệnh hiện tại hoàn tất, vì vậy “Hello” là đầu tiên và “World” – sau nó.
Trong trình duyệt, có một giới hạn về tần suất các bộ hẹn giờ lồng nhau có thể chạy. Tiêu chuẩn HTML5 standard cho biết: “sau năm bộ hẹn giờ lồng nhau, khoảng thời gian buộc phải ít nhất 4 mili giây.”.
Hãy chứng minh ý nghĩa của nó với ví dụ bên dưới. Lệnh gọi setTimeout trong nó tự lên lịch lại với độ trễ bằng không. Mỗi cuộc gọi ghi nhớ thời gian thực từ cuộc gọi trước đó trong mảng thời gian. Sự chậm trễ thực sự trông như thế nào? Hãy xem ví dụ sau:
1 2 3 4 5 6 7 8 9 10 11 12 |
let start = Date.now(); let times = []; setTimeout(function run() { times.push(Date.now() - start); // nhớ độ trễ từ cuộc gọi trước if (start + 100 < Date.now()) alert(times); // Hiển thị độ trễ sau 100ms else setTimeout(run); // lên lịch lại }); // Kết quả // 1,1,1,1,9,15,20,24,30,35,40,45,50,55,59,64,70,75,80,85,90,95,100 |
Đầu tiên các cuộc gọi được gọi ngay lập tức, nhưng sau đó chúng ta có thể thấy 9, 15, 20, 24
… Độ trễ bắt buộc trên 4 ms giữa các lần gọi có hiệu lực.
Điều tương tự cũng xảy ra nếu chúng ta sử dụng setInterval
thay vì setTimeout
: setInterval (f)
chạy vài lần với độ trễ bằng 0 và sau đó với độ trễ trên 4 ms.
Đối với JavaScript phía máy chủ, hạn chế đó không tồn tại và tồn tại các cách khác để lập lịch công việc không đồng bộ ngay lập tức, như setImmediate cho Node.js. Vì vậy, lưu ý này dành riêng cho từng trình duyệt.
Kết luận
Cùng ôn lại một số kiến thức chính về hàm setTimeout() và setInterval() trong JavaScript đã học trong bài viết này nhé:
- Phương thức
setTimeout(func, delay, ...args)
vàsetInterval(func, delay, ...args)
cho phép chúng ta chạy functionfunc()
một hoặc lặp đi lặp lại saudelay
milliseconds. - Để hủy thực thi, chúng ta nên gọi
clearTimeout / clearInterval
với giá trị được trả về bởisetTimeout / setInterval
. - Lệnh gọi
setTimeout
lồng nhau là một giải pháp thay thế linh hoạt hơn chosetInterval
, cho phép chúng tôi đặt thời gian giữa các lần thực hiện chính xác hơn. - Lập lịch với độ trễ bằng không với
setTimeout (func, 0)
(hoặcsetTimeout (func)
) được sử dụng để lập lịch cuộc gọi “càng sớm càng tốt, nhưng sau khi tập lệnh hiện tại hoàn tất”. - Trình duyệt giới hạn độ trễ tối thiểu cho năm cuộc gọi lồng nhau trở lên của
setTimeout
hoặcsetInterval
(sau cuộc gọi thứ 5) là 4ms.
Hãy lưu ý rằng tất cả các phương thức lên lịch trình không đảm bảo độ trễ chính xác.