Lời dẫn - dạo đầu - trước khi vào vấn đề chính.

Khoảng hơn 3 năm về trước, vào năm 2019, khi tôi bắt đầu chập chững bước vào cái nghề mà thiên hạ (mà chủ yếu là các VOZer) kháo nhau là vua của mọi nghề - IT. Như bao sinh viên mới và chuẩn bị ra trường khác, tôi bắt đầu sự nghiệp gõ thuê tại một công ty outsource lớn giấu tên F nào đó ở phố Duy Tân, Hà Nội. Và JavaScript là thứ ngôn ngữ mà tôi bắt đầu và nó bám theo tôi đến tận bây giờ dù tôi đã code ít đi nhiều rồi.
Chỉ là ảnh minh họa cho công ty F giấu tên.
Chỉ là ảnh minh họa cho công ty F giấu tên.
Tại thời điểm đó, như đã nói ở trên, như bao sinh viên hoặc các Fresher/Junior developer khác, tôi cũng chỉ quan tâm đến việc code tôi gõ (mà chủ yếu là copy từ source có sẵn/stackoverflow) có chạy hay không. Và tôi đã mất gần 2 năm trời tư duy một cách thiểu não như thế, cho đến một ngày, trong một buổi trà đá với mấy ông em đồng nghiệp đến sau tại công ty outsource C dấu tên nào đó khác, tôi được hỏi về Promises/Asynchronous/Callstack/Event loop/Single thread các thứ,.. Tôi bất chợt ngớ người ra, như một cú tát thẳng vào mặt, và tôi bị lời nói của thằng ranh đó ám ảnh đến mất ngủ đêm hôm đó. Tôi như được giác ngộ ra rằng tôi có một sứ mệnh rằng tôi phải làm gì đó để giúp đỡ những sinh viên, những dev fresher khác tránh khỏi vết xe đổ mà tôi đã đi qua.
Bài viết không mang tính học thuật, chỉ có tính tham khảo, và có lẽ chủ yếu để giải trí.
Dao Duy Tan
Trước khi đi bóc tách từng vấn đề thì đây sẽ là bức tranh tổng quan mình muốn bạn đọc có thể nắm được sau khi đọc xong bài viết này.
How the heck does JS work eventually?
How the heck does JS work eventually?

Vào đề, về JavaScript, ngắn gọn.

JavaScript - JS là một ngôn ngữ lập trình nhẹ, đơn luồng, thông dịch, dùng trong phát triển ứng dụng web. (đấy là trên mạng nói thế).
Để ý thấy chữ đơn luồng được bôi đen chứ? Đây sẽ là thứ mà chúng ta sẽ cần phải quan tâm vì đó chính là nguồn cơn cho những thứ khá đau đầu phải "tiêu hóa" tiếp theo.

Đơn luồng - call stack - blocking

Câu chuyện bắt đầu với đơn luồng, vậy đơn luồng trong JavaScript là như thế nào? Đơn giản thì nó chỉ có duy nhất một call stack. Và tại một thời điểm chỉ có duy nhất một công việc được thực hiện, một đoạn code được thực thi
Lại một thuật ngữ khác, callstack là gì? Đơn giản thì callstack là một data structure ghi lại nơi mà đoạn code chương trình đang thực thi, khi một đoạn code/function được gọi đến, nó sẽ được đưa vào stack, và khi nó được chạy xong, nó sẽ được đưa ra khỏi stack. (LIFO - Last In, First Out)
Để hiểu rõ hơn về 2 ý mà tôi đã hightlight ở trên thì bạn có thể chạy block code đơn giản ở link dưới đây: How this code block work with visualization.
Cho những bạn nào lười bấm vào link thì đại loại nó như này.
Cho những bạn nào lười bấm vào link thì đại loại nó như này.
Và như này
Và như này
Tôi khuyên chân thành các bạn nên bỏ qua đoạn tôi viết dưới đây mà bấm vào link trên để tự mình kiểm tra vì đường link này có kèm animation minh họa rất dễ hiểu và tôi đảm bảo đoạn giải thích của tôi sẽ làm các bạn rối hơn nữa.
Như ví dụ đơn giản trên hình, ta có một đoạn code thực hiện việc in ra tổng của hai số. Trong đó hàm sum để tính tổng, hàm log để in ra console, và hàm main chỉ đơn giản để gọi hàm log.
Khi đoạn code này được thực thi, hàm main() sẽ được chạy đầu tiên và được đưa vào callstack.
Trong hàm main() sẽ tiếp tục thực thi hàm log() -> log() đưa vào callstack. Lúc này trong hàm log() lại gọi tới hàm sum() -> đưa vào callstack và thực hiện phép tính toán a+b -> đưa vào callstack.
Tại thời điểm a+b được thực hiện xong -> xóa khỏi callstack, tức hàm sum() đã được thực thi xong -> xóa khỏi callstack. được thực thi xong -> xóa khỏi callstack.
Ngay sau khi sum() được xóa khỏi CS, hàm console.log() được gọi tới -> đưa vào callstack -> thực hiện xong -> xóa khỏi callstack -> log() được thực thi xong và xóa khỏi callstack.
Và khi log() được xóa khỏi callstack, bên trong hàm main() không còn gì để làm cả -> main được thực thi xong và xóa khỏi callstack, kết thúc đoạn code trên.
Hi vọng các bạn đã có thể hiểu được những điều đơn giản nhưng dưới văn giải thích của tôi nó lại trông rối rắm.

Bonus:

Trong phần mở đầu, tôi có nhắc đến stackoverflow, và lỗi dưới đây tôi nghĩ cũng có rất nhiều bạn đã từng gặp phải khi sử dụng đệ quy trong code của mình. Vậy các bạn có từng hình dung nó sẽ trông như nào chưa?
Tràn stack nó sẽ trông như thế này nếu bạn lười click vào link  trên
Tràn stack nó sẽ trông như thế này nếu bạn lười click vào link trên

Về Blocking

Nếu một trong những tác vụ trong callstack mà tốn quá nhiều thời gian xử lí thì sao? Ví dụ khi bạn xử lí một chiếc ảnh thật nặng, hay thực thi một đoạn logic tính toán phức tạp, hay đơn giản hơn là cần gọi một HTTP request trong khi dùng mạng ADSL thì điều gì sẽ xảy ra? Khi đó trình duyệt của bạn sẽ không thể render, không thể click hay thực hiện một hành động nào khác, đây chắc chắn không phải là một trải nghiệm tốt cho người dùng.
Và khi trình duyệt phải xử lí quá nhiều các tác vụ nặng trong callstack, thì đây sẽ là thứ mà nó sẽ là thứ mà chúng ta nhận được.
Một trải nghiệm chúng ta cùng ghét
Một trải nghiệm chúng ta cùng ghét
Vậy làm sao để mang lại những trải nghiệm tốt cho người dùng, để trình duyệt có thể thở dễ dàng hơn? Chúng ta có vị cứu tinh là Asynchronous Callback, mà tôi sẽ viết trong phần sau.
Bài viết được lấy cảm hứng từ video dưới đây, tôi nghĩ khi bạn xem xong video này thì bạn cũng không cần đọc phần 2 (phần mà tôi nghĩ sẽ còn rất lâu nữa tôi mới có thể hoàn thành) của tôi làm gì.