- vừa được xem lúc

Event loop và callback queue trong JavaScript

0 0 11

Người đăng: Thavrith

Theo Viblo Asia

JavaScript là ngôn ngữ lập trình đơn luồng (một câu lệnh (dòng code) được thực thi tại một thời điểm) và có mô hình thực thi đồng bộ (mỗi câu lệnh sẽ được thực thi theo thứ tự xuất hiện).

Vậy phải thế nào khi ta cần đợi một khoảng thời gian trước khi có thể thực thi một số đoạn code? Có thể là ta phải chờ dữ liệu từ API/server request, hay cần đợi cho timer hoàn thành và thực thi code.

Dẫn nhập

Ta có chương trình như sau:

Khi ta chạy chương trình, nó sẽ thực thi, lần lượt như sau:

1. Khai báo function printHello ở global memory. 2. Dòng 5 - Thực thi setTimeout(printHello,1000). Lúc này nhiệm vụ được chuyển sang Web Browser Feature: Timer.

Nhưng ta đã gửi gắm gì cho Timer? Là duration: 1000ms kèm theo printHello

Lúc này ta chuyển đến Web Browser Features và set một Timer cho việc này. Và vì lúc này là 1ms, status của Timer chưa hoàn thành, nên ta sẽ trở lại global.

3. Trở lại với global, thread of execution đi tới và thực thi dòng 7 - in ra Me first!

Lưu ý: Khoảng thời gian qua, bộ đếm thời gian của Timer vẫn đang tíc tắc tíc tắc nhé.

4. Khi thời gian từ lúc bắt đầu chạy chương trình đạt 1000ms, trạng thái của Timer được xác nhận hoàn thành và dựa trên On Completion, function printHello sẽ được đẩy vào Call stack và hàm printHello sẽ được thực thi, in ra Hello

Event Loop & Callbacks Queue

JavaScript engine ưu tiên thế nào khi thực thi code?

  • Code ở global sẽ được thực thi trước, theo thứ tự xuất hiện.
  • Sau đó nó kiểm tra xem call stack có đang trống không? Nếu call stack trống, nó sẽ lấy code ra từ callback queue - nơi chúng ta thêm vào callbacks function.

Nghĩa là nếu global đã thực thi xong và call stack trống, JS engine sẽ đi đến callbacks queue để lấy callback function và đẩy vào call stack để thực thi.

Nhưng cái gì đi kiểm tra những thứ trên? Event loop!

Event loop, là tính năng mà:

  • Đi kiểm tra xem global còn code không? Nếu còn thì thực thi nó.
  • Kiểm tra xem call stack có gì không? Nếu có thì thực thi nó.
  • Nếu tất cả đã thực thi xong, chuyển tới callback queue và bắt đầu từ cái cũ nhất - lấy nó và thêm vào call stack để thực thi.

Quay trở lại với chương trình ở trên

1ms printHello sẽ được đẩy vào Callback queue, sau đó chương trình thực thi bình thường. Khi thời gian từ lúc bắt đầu chạy chương trình đạt 1000ms:

  • printHello: Ê tao hết thời gian bị hoãn rồi, cho tao trở lại đi.
  • Event loop: Uh rồi, biết mày được phép trở lại rồi, để tao coi coi tới lượt mày chưa.

Sau đó nó làm nhiệm vụ của nó là kiểm tra global execution context đã thực thi tất cả code ở global chưa, và xem call stack có đang trống hay không. Nếu các điều kiện kia thoả mãn thì nó sẽ đi đến callback queue và lấy printHello ở đó và đẩy vào call stack.

Nhưng nếu ở ví dụ trên, tại thời điểm 1000ms mà 2 điều kiện trên chưa thoả mãn thì sao? Ta sẽ hoàn toàn hiểu khi xem ví dụ tiếp theo.

Do sau 3 bài viết chúng ta đã quen thuộc với thread of execution, execution context, call stack nên mình sẽ đi nhanh hơn mà không đi từng bước cách chương trình được thực thi nữa.

1. Tại thời điểm 1ms dòng 9 được thực thi - setTimeout(printHello,0).

Ta đã gửi gắm gì cho Timer? Là duration: 0ms kèm theo printHello.

Lúc này ta chuyển đến Web Browser Features và set một Timer cho việc này, Lúc này là 1ms, duration là 0ms, status của Timer đã hoàn thành, vậy ta có đưa printHello về call stack và thực thi nó luôn không? Không!

Hãy nhớ lại ở phần event loop, ta có nói chỉ khi thoả các điều kiện sau thì mới tới lượt code trong callback queue được chạy:

  • Global execution context đã thực thi xong hết code ở global
  • Call stack trống

Ngay lúc này ở global vẫn còn code chưa được thực thi, nên event loop sẽ chưa xem tới callback queue. Toàn cảnh lúc này đang là:

2. Với 1 trong 2 điều kiện chưa thoả mãn, ta quay lại global - lúc này đang là 2ms và ở dòng 11 đẩy blockFor1Sec() vào call stack để thực thi. Function này làm gì kệ nó, cứ giả định là nó sẽ tốn của chúng ta 1s hay 1000ms.

3. Lúc này đang là 1002ms, blockFor1Sec đã thực thi xong, call stack đã trống. Nhưng ở global vẫn còn code để thực thi!

Đến dòng 13 và thực thi, in ra Me first!

4. Lúc này, code ở global đã thực thi xong hết, call stack cũng trống, đã thoả điều kiện để event loop đi gặp callback queue xem có gì cần gì không. Và vì có printHello đã đợi từ lâu, nó được đẩy vào call stack và thực thi, in ra Hello

Lúc này chương trình đã thực thi xong nên global execution context sẽ bị xoá.

Kết bài

Vậy là ta đã tìm hiểu về event loop, và callback queue hoạt động như thế nào, ở bài sau ta sẽ tìm hiểu về micro task queue và Promise.

Bài này nằm trong chuỗi bài viết về JavaScript của em/ mình khi đang học, nếu có hiểu sai hay còn thiếu xót mong các bạn, anh chị góp ý!

Em cũng đang tìm việc về Python (Django), JavaScript fresher/intern ạ. Mong có cơ hội làm việc với mọi người.

Bình luận

Bài viết tương tự

- vừa được xem lúc

Cuối cùng thì Event loop là gì?

Đặt vấn đề. Vài tháng trước, mình có một buổi presentation về Javascript core nên cũng có tìm hiểu qua về một số khái niệm cơ bản và hay ho như nhân V8 (Google), Event-Driven, Non-blocking I/O, Event

0 0 27

- vừa được xem lúc

Event Loop trong Node.js

Giới thiệu. Event Loop là một trong những khái niệm cực kì quan trọng khi chúng ta tìm hiểu về Node.js. .

0 0 21

- vừa được xem lúc

Promise & microtask queue trong JavaScript

Promise. Promise là một special object được tích hợp trong JavaScript.

0 0 14

- vừa được xem lúc

Blog#173: Introduction to Advanced JavaScript Concepts - Advanced JavaScript Part 1

Hi, I'm Tuan, a Full-stack Web Developer from Tokyo . JavaScript is an essential language for web development, and learning advanced concepts can significantly improve your programming skills.

0 0 11

- vừa được xem lúc

Blog#174: 🤔Understanding Asynchronous JavaScript: 🔄Callbacks, 🤞Promises, and 🤝Async/Await

Hi, I'm Tuan, a Full-stack Web Developer from Tokyo . 1. Introduction to Asynchronous JavaScript. What is Asynchronous JavaScript.

0 0 12