Như anh em đã biết, JavaScript là ngôn ngữ lập trình đơn luồng (single-threaded), nghĩa là nó chỉ có thể thực thi một tác vụ tại một thời điểm. Nếu một tác vụ mất quá nhiều thời gian (Long Task) để hoàn thành, nó có thể khiến trình duyệt trở nên không phản hồi hoặc chậm.
Để tối ưu một tác vụ dài (Long Task), trước em anh em cần nắm rõ về Event Loop.
Event Loop
Event Loop là 1 cơ chế trong JavaScript Runtime, giúp quản lý và xử lý các event, task và các thao tác bất đồng bộ.
▶ Các bước thực hiện trong 𝗘𝘃𝗲𝗻𝘁 𝗟𝗼𝗼𝗽:
- Event Loop liên tục kiểm tra 2 queue: Task Queue và Microtask Queue. Khi Call Stack trống, chuyển qua bước 2.
- Nếu Microtask Queue có chứa task, Event Loop sẽ chuyển và thực thi tất cả các task đó trên Call Stack trước khi kiểm tra Task Queue.
- Nếu Task Queue có task, Event Loop sẽ chuyển task đầu tiên trong Task Queue và thực thi nó trên Call Stack.
- Nếu task trong bước 3 hoàn thành và tạo ra các task mới trên Microtask Queue, Event Loop sẽ lặp lại bước 2. Khi Microtask Queue trống, lặp lại bước 3.
💥 𝗡𝗼𝘁𝗲:
- Microtask Queue gồm: Callbacks from Promises (then, catch, finally), Callbacks from MutationObserver, queueMicrotask, ...
- Task Queue gồm: setTimeout/setInterval, callback events (onscroll, onclick, ...), ...
- Thứ tự thực hiện của Event loop: Synchronous code > Microtasks > Macrotasks.
- Nếu gặp await, mọi thứ sau await sẽ được đưa vào Microtask Queue.
- Macrotasks chỉ thực thi khi Call Stack và Microtask Queue trống.
- Nếu liên tục đẩy các task vào Microtask Queue, trình duyệt sẽ bị lag.
Oke anh em mình cùng bắt đầu chủ đề chính nào, mình sẽ đưa ra 1 vài ví dụ để anh em có thể nắm chắc được kỹ thuật tối ưu Long Task này.
Tối ưu Long task
Ví dụ 1:
Đây là một Long Task (tác vụ dài), khiến trình duyệt bị lag và người dùng không thể thao tác gì thêm.
Chạy Performance tab, anh em có thể thấy Long Task đang chiếm hết main thread và khiến trình duyệt bị lag.
Anh em có thể tối ưu hóa Long Task trên bằng cách kết hợp setTimeout và chia nhỏ Long Task thành 10 Task nhỏ. Các Task nhỏ này sau đó sẽ được đưa vào MacroTask Queue.
Chạy Performance lần nữa, anh em có thể thấy lúc này có nhiều Task nhỏ hơn đang chạy.
Phóng to Task lên, anh em có thể thấy ở giữa các Task nhỏ, có thêm các Task khác ví dụ như xử lý tương tác người dùng hoặc render UI. Đó là lý do tại sao trình duyệt vẫn mượt mà.
Ví dụ 2:
Đến 1 ví dụ thực tế hơn nhé anh em. Trong ví dụ này, chúng ta có hàm saveSettings bao gồm 5 hàm con bên trong để xử lý việc hiển thị giao diện, lưu dữ liệu vào db và phân tích dữ liệu.
Nếu chúng ta gọi 5 hàm này một cách đồng bộ, nó có thể tạo ra một Long Task, khiến trình duyệt chậm và không phản hồi. Chạy Performance ta có kết quả sau.
Để cải thiện hiệu năng, chúng ta sẽ thực hiện các công việc quan trọng (critical works) trước và trì hoãn các công việc không quan trọng (non-critical works) (các công việc không hiển thị với người dùng) bằng cách sử dụng setTimeout.
Hoặc một cách tương tự để trì hoãn các công việc không quan trọng (non-critical works), được gọi là yield.
Chạy Performance lần nữa, dưới đây là kết quả của cả 2 cách tối ưu trên.
Phóng to Task lên, anh em có thể thấy ở giữa các Task nhỏ, có thêm các Task khác ví dụ như xử lý tương tác người dùng hoặc rendering. Đó là lý do giúp trình duyệt vẫn mượt mà.
Kết
Hi vọng qua 2 ví dụ trên, anh em đã nắm được kỹ thuật tối ưu Long Task để áp dụng ngay vào dự án của mình.