Event loop là gì?
Tìm hiểu về cách hoạt động của event loop trong Javascript để hiểu cách Javascript xử lý nhiều tác vụ cùng lúc.
Javascript là ngôn ngữ lập trình single-threaded, có nghĩa là tại một thời điểm Javascript chỉ có thể thực thi một tác vụ, khi phải thực thi nhiều tác vụ, Javascript phải thực hiện chúng tuần tự. Nhưng có vẻ điều này không đúng lắm? Rõ ràng khi sử dụng các ứng dụng web, ta vẫn có thể thực hiện nhiều việc cùng lúc, ví dụ như ta hoàn toàn có thể vừa nghe nhạc, vừa tải file từ chính trang web đó.
Để hiểu được điều gì đã xảy ra, hãy xem xét một ví dụ đơn giản như sau.
function third() {
console.log("third")
}
function second() {
console.log("second");
third();
}
function first() {
console.log("first");
second();
}
first();
// first
// second
// third
Ở đây chúng ta có 3 hàm là first, second và third. Hàm first in ra màn hình console từ “first” rồi sau đó gọi hàm second, hàm second in ra màn hình từ “second” rồi gọi hàm third, hàm third đơn giản là in ra từ “third”. Kết quả khi thực thi chương trình này là màn hình console sẽ hiện ra “first“ “second“ “third“
Khi thực thi một hàm, Javascript sẽ push (thêm) hàm đó vào một cái “Call“ Stack (Stack là một loại cấu trúc dữ liệu với đặc điểm là first in last out). Đến khi hàm đó return (kết thúc), thì nó sẽ được pop (lấy ra) khỏi call stack. Thử áp dụng điều này vào chương trình ở trên, quá trình sẽ diễn ra như sau:
Đầu tiền hàm first sẽ được push vào Call Stack
Hàm first gọi hàm console(“first“) nên console(“first“) sẽ được thêm vào Call Stack. Hàm console(“first“) in ra màn hình từ “first“ và kết thúc nên nó sẽ được pop ra khỏi Call Stack.
Hàm second sẽ được push vào trong Call Stack.
Hàm second gọi hàm console(“second“) nên console(“second“) cũng sẽ được thêm vào Call Stack. Tương tự ở trên, console(“second“) in ra màn hình từ “second“ rồi kết thúc nên nó được pop ra khỏi Call Stack.
Và mọi thứ sẽ được thực thi tương tự cho đến khi hàm console.log(“third“) thực thi xong và được pop ra khỏi Call Stack, đến lúc này hàm third cũng đã xong nên nó cũng sẽ bị pop ra khỏi Call Stack, rồi đến hàm second và hàm first.
Vậy sẽ ra sao nếu ở ở hàm second, thay vì chỉ gọi hàm console.log(“second“), chúng ra còn lại muốn fetch data từ một remote API.
function second() {
console.log("second");
// Có thể mất một thời gian để trả về kết quả
fetch("https://jsonplaceholder.typicode.com/todos/1").then((response) => {
console.log("fetch done");
})
third();
}
Vì Javascript chỉ có thể xử lý một tác vụ tại một thời điểm, nên chúng ta sẽ nghĩ ngay đến việc là hàm second sẽ tốn một khoảng thời gian để thực hiện và hàm third sẽ phải chờ. Nhưng trên thực tế không phải là vậy.
Javascript engine không bao giờ hoạt động một mình, nó luôn hoạt động trong một môi trường nào đó, ví dụ là môi trường của browser khi chúng ta phát triển web app hoặc là môi trường nodejs khi chúng ta phát triển các ứng dụng backend. Ở đây mình sẽ chỉ đề cập đến nền tảng browser, nền tảng nodejs cũng sẽ xảy ra tương tự. Khi chúng ta dùng hàm fetch để fetch data từ remote API, hàm fetch không phải là một phần của Javascript engine mà nó là một phần của Web API.
Khi gọi hàm fetch, chúng ta “đăng ký” thêm một hàm callback ở hàm “then”, Javascript engine sẽ báo cho Web API rằng “Hey Web API, hãy giúp tôi thực hiện hàm fetch này, khi nào thực hiện xong và nhận được kết quả, hãy gọi hàm callback này nhé“. Sau đó Javascript engine tiếp tục công việc của mình và gọi hàm third.
Về phía của Web API, sau khi đã fetch thành công data từ remote API, nó sẽ insert cái hàm callback ở trên cùng với data trả về vào một cái Callback Queue (Queue là một loại cấu trúc dữ liệu với đặt điểm là first in first out) (Thực ra ở đây sẽ có 2 loại Queue là Task Queue vào Job Queue, mình sẽ nói về nó ở bài về Promise, nếu mình có viết). Lúc này, event loop sẽ vào việc.
Nhiệm vụ của event loop khá đơn giản, nó sẽ theo dõi Call Stack, nếu Call Stack trống (empty), nó sẽ lấy cái callback ở trong Callback Queue và đặt nó vào Call Stack, sau đó sẽ bắt đầu thực thi cái callback đó.
Okay, vậy hãy thử thực thi lại chương trình của chúng ta khi có thêm phần gọi fetch:
function third() {
console.log("third")
}
function second() {
console.log("second");
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then((response) => {
console.log("fetch done");
})
third();
}
function first() {
console.log("first");
second();
}
first();
Đầu tiền hàm first sẽ được push vào Call Stack
Hàm first gọi hàm console(“first“) nên console(“first“) sẽ được thêm vào Call Stack. Hàm console(“first“) in ra màn hình từ “first“ và kết thúc nên nó sẽ được pop ra khỏi Call Stack.
Hàm second sẽ được push vào trong Call Stack.
Hàm second gọi hàm console(“second“) nên console(“second“) cũng sẽ được thêm vào Call Stack. Hàm console(“second“) in ra màn hình từ “second“ và kết thúc nên nó sẽ được pop ra khỏi Call Stack.
Hàm fetch được gọi, nó được thêm vào Call Stack. Hàm fetch sẽ được Web API xử lý, nên hàm fetch bên trong Call Stack sẽ được Pop ra.
Hàm third sẽ được push vào trong Call Stack.
Hàm third gọi hàm console(“third“) nên console(“third“) sẽ được thêm vào Call Stack. Hàm console(“third“) in ra màn hình từ “third“ và kết thúc nên nó sẽ được pop ra khỏi Call Stack. Lúc này hàm third cũng đã thực thi xong, nó được pop ra khỏi Call Stack, sau đó là đến hàm second vào cuối cùng là hàm first.
Sau khi tất cả các hàm đều đã được pop ra khỏi Call Stack. Call Stack sẽ trống. Event loop lúc sẽ bắt đầu lấy cái callback đã được đăng ký ở hàm fetch từ trong Callback Queue ra (giả sử lúc này API đã được fetch xong), đưa nó vào Call Stack.
Hàm callback gọi hàm console(“fetch done“) nên console(“fetch done“) cũng sẽ được thêm vào Call Stack. Hàm console(“fetch done“) in ra màn hình từ “
fetch done
“ và kết thúc nên nó sẽ được pop ra khỏi Call Stack. Sau đó hàm callback cũng sẽ được pop ra khỏi Call Stack.Chương trình của chúng ta kết thúc.
Kết quả màn hình console sẽ được in ra như sau
first
second
third
fetch done
Vậy là mình đã giới thiệu qua cho các bạn về cách hoạt động của event loop. Hi vọng các bạn có thể áp dụng được những kiến thức này cho công việc thực tế của mình. Hẹn gặp lại các bạn ở những bài viết sau.
References:
Nếu các bạn không muốn đọc bài viết của mình thì có thể xem video này.
https://medium.com/sessionstack-blog/how-javascript-works-event-loop-and-the-rise-of-async-programming-5-ways-to-better-coding-with-2f077c4438b5
https://exploringjs.com/es6/ch_async.html