Hướng dẫn asynchronous trong javascript - không đồng bộ trong javascript
1. Giới ThiệuChào mọi người, hôm nay chúng ta cùng tìm hiểu về Synchronous (Lập trình đồng bộ) là gì? Asynchronous (Lập trình bất đồng bộ) là gì? Synchronous và Asynchronous hoạt động như thế nào trong Javascript nhé! Show
2. Tổng quan:SynchronousVậy Synchronous là gì? Hãy tưởng tưởng đến việc một quán ăn nhỏ có 2 bàn và chỉ có 1 người phục vụ bàn. Lúc này có 2 nhóm khách hàng lần lượt vào quán, người phục vụ sẽ thực hiện yêu cầu gọi món của nhóm thứ nhất, tiến hành thông báo cho nhà bếp chế biến món ăn và chờ món ăn sau khi được hoàn thành và dọn ra cho nhóm thứ nhất. Sau khi hoàn thành toàn bộ yêu cầu của nhóm thứ nhất thì người phục vụ mới bắt đầu chuyển sang nhóm thứ hai để lấy yêu cầu của họ và xử lí. Đây chính là xử lí đồng bộ, tức là mọi việc đều được xử lí theo thứ tự từng bước, chỉ khi nào bước đầu tiên xong thì bước thứ hai mới được thực hiện. Trong trường hợp này, xử lí đồng bộ thật khoai khi nhóm thứ hai phải chờ nhóm thứ nhất được phục vụ xong mới đến lượt mình?? Tuy nhiên, đó chỉ là ví dụ về một trường hợp bất lợi của Synchronous, do đó, chúng ta hãy tiếp tục bàn luận về ưu điểm và khuyết điểm của nó nhé. Ưu điểm:
Khuyết điểm:
AsynchronousVậy Asynchronous là gì? Hãy cùng tiếp tục với ví dụ ở phần Synchronous nhé. Khi 2 nhóm khách hàng lần lượt vào quán, người phục sẽ lần lượt lấy yêu cầu của từng nhóm rồi đồng thời đưa chúng cho đầu bếp để chế biến, sau khi món ăn của bất kì nhóm nào được chế biến xong thì người phục vụ sẽ mang ra cho nhóm đó. Điều này có nghĩa là cả nhóm 1 và nhóm 2 đều được lấy yêu cầu và xử lí gần như cùng lúc chứ không phải chờ đợi nhau như Synchronous. Trong trường hợp này rõ ràng xử lí bất đồng bộ chiếm ưu thế hơn xử lí đồng bộ, tuy nhiên, nó cũng có ưu và nhược điểm riêng, hãy cùng bàn luận về nó nhé: Ưu điểm:
Khuyết điểm:
Khi nói về UX, nó gây ra cảm giác giật lag cho người dùng vì họ không thể thực hiện thao tác khác nếu thao tác trước đó chưa được xử lí xong.Asynchronous Vậy Asynchronous là gì?Event loop là gì, và cách hoạt động như thế nào. Bạn có thể tham khảo video tại đây để hiểu rõ hơn về cách hoạt động của nó. Mình sẽ tóm gọn lại một số thuật ngữ như sau:
3. Synchronous trong Javascript: JavaScript mặc định là ngôn ngữ lập trình đồng bộ, blocking và Single-thread (đơn luồng), có nghĩa là một thao tác sẽ được tiến hành tại một thời điểm, trên một luồng chính duy nhất và mọi thứ khác bị chặn cho đến khi thao tác đó hoàn thành.
Trước khi xem qua ví dụ, bạn cần biết khái niệm Event loop là gì, và cách hoạt động như thế nào. Bạn có thể tham khảo video tại đây để hiểu rõ hơn về cách hoạt động của nó. Mình sẽ tóm gọn lại một số thuật ngữ như sau:
Heap: là nơi tất cả việc phân bổ bộ nhớ xảy ra đối với các biến đã xác định trong chương trình của mình. câu lệnh console.log("Start") được push vào call stack, và sau đó pop ra ngoài Callstack: là một ngăn xếp với cơ chế LIFO (last in first out), code sẽ được đẩy vào và thực thi sau đó bật ra khi thực hiện xong. Và vì Javascript là ngôn ngữ đơn luồng nên chỉ có duy nhất 1 call stack. câu lệnh console.log(i) với giá trị i đầu tiên là 0 được push vào call stack và pop ra ngoài Web APIs: Bao gồm DOM Events ( sự kiện onClick, onLoad ...), ajax (XMLHttpRequest), setTimeout, ... Nó giúp đẩy các job ra bên ngoài và chỉ tạo ra các sự kiện kèm theo các handler gắn với các sự kiện. câu lệnh console.log(i) với giá trị i = 1 được push vào call stack và pop ra ngoài ... Step 11: câu lệnh console.log(i) với giá trị i = 9 được push vào call stack và pop ra ngoài câu lệnh console.log(i) với giá trị i = 9 được push vào call stack và pop ra ngoài Có thể bạn quan tâmStep 12: câu lệnh console.log("End") được push vào call stack, và sau đó pop ra ngoài câu lệnh console.log("End") được push vào call stack, và sau đó pop ra ngoài Như các bạn có thể thấy, dòng lệnh console.log('End') không hề được thực hiện cho đến khi vòng lặp được kết thúc. Giả sử nếu vòng lặp này có số vòng cần lặp là 10,000 thì nó sẽ tạo ra blocking khiến các câu lệnh sau đó như console.log("End") rất lâu sau mới được thực hiện. Việc này trở nên rất nghiêm trọng khi chúng ta làm việc trên một ứng dụng lớn với nhiều yêu cầu của máy chủ. Thật may mắn khi Javascript đã cung cấp cho chúng ta một giải pháp đó là Asynchronous. 4. Asynchronous trong Javascript:Vậy Asynchronous hoạt động như thế nào trong Javascript? Cùng nhau xem qua đoạn code của quá trình xử lí đồng bộ này nhé:
Kết quả in ra như sau:
Step 1: câu lệnh console.log("Start") được push vào call stack, và sau đó pop ra ngoài câu lệnh console.log("Start") được push vào call stack, và sau đó pop ra ngoài Step 2: câu lệnh setTimeout() được push vào call stack nhưng không được thực thi và lập tức được gửi sang WebAPIs để xử lí, lúc này WebAPIs sẽ chứa 1 timer 1000ms và một callback function (ở ví dụ trên là arrow function) và sau 1000ms, funtion này sẽ được gửi sang Callback Queue để chờ được xử lí câu lệnh setTimeout() được push vào call stack nhưng không được thực thi và lập tức được gửi sang WebAPIs để xử lí, lúc này WebAPIs sẽ chứa 1 timer 1000ms và một callback function (ở ví dụ trên là arrow function) và sau 1000ms, funtion này sẽ được gửi sang Callback Queue để chờ được xử lí Step 3: câu lệnh console.log("End") được push vào call stack, và sau đó pop ra ngoài câu lệnh console.log("End") được push vào call stack, và sau đó pop ra ngoài Step 4: sau 1000ms, Event Loop kiểm tra Callback Queue và thấy tồn tại callback, lúc này kiểm tra thấy callstack đang trống nên nó push callback vào trong callstack và thực hiện xử lí sau 1000ms, Event Loop kiểm tra Callback Queue và thấy tồn tại callback, lúc này kiểm tra thấy callstack đang trống nên nó push callback vào trong callstack và thực hiện xử lí Step 5: câu lệnh console.log("Middle") bên trong callback được push vào call stack, và sau đó pop ra ngoài, đồng thời callback ở trên cũng được pop ra khỏi callstack câu lệnh console.log("Middle") bên trong callback được push vào call stack, và sau đó pop ra ngoài, đồng thời callback ở trên cũng được pop ra khỏi callstack Trong Javascript, bất đồng bộ xảy ra khi chúng ta thực hiện các thao tác bất đồng bộ ví dụ:
Tuy nhiên, như đã nói ở trên, xử lí bật đồng bộ khiến chúng ta khó kiểm soát code, và để làm cho các câu lệnh được thực hiện theo đúng thứ tự của nó, chúng ta có 3 phương án chính để giải quyết vấn đề này:
CallbackCallback có nghĩ là một function được truyền vào một function khác dưới dạng tham số và được thực thi bên trong function đó. Ví dụ: Kết quả in ra như sau:
Step 1: câu lệnh console.log("Start") được push vào call stack, và sau đó pop ra ngoài Step 2: câu lệnh setTimeout() được push vào call stack nhưng không được thực thi và lập tức được gửi sang WebAPIs để xử lí, lúc này WebAPIs sẽ chứa 1 timer 1000ms và một callback function (ở ví dụ trên là arrow function) và sau 1000ms, funtion này sẽ được gửi sang Callback Queue để chờ được xử lí PromiseCallback Callback có nghĩ là một function được truyền vào một function khác dưới dạng tham số và được thực thi bên trong function đó.
=> Callback function là một cách thức phổ biến, dễ hiểu, và dễ sử dụng, tuy nhiên nếu sử dụng quá nhiều Callback lồng nhau thì sẽ xảy ra tình trạng Callback Hell (tức là hàm lồng nhau) dẫn đến việc code khó hiểu, khó debug và khó maintain. Ví dụ về Callback Hell
resolve: một function sẽ được gọi nếu đoạn code bất đồng bộ trong Promise chạy thành công.
reject: một function sẽ được gọi nếu đoạn code bất đồng bộ trong Promise có lỗi xảy ra.
Promise cũng cung cấp cho chúng ta 2 phương thức để xử lý sau khi được thực hiện:
then(): Dùng để xử lý sau khi Promise được thực hiện thành công (khi resolve được gọi). catch(): Dùng để xử lý sau khi Promise có bất kỳ lỗi nào đó (khi reject được gọi).Promise hell, tương tự như Callback Hell
finally(): Dùng để xử lý sau khi Promise được thực hiện thành công hoặc thất bại (resolve hoặc reject được gọi)Promise chain để biến đổi đoạn code bị Promise hell bên trên nhé 0Cấu trúc: Ví dụ cụ thể: Ngoài ra, chúng ta có thể nối nhiều Promise với nhau bằng 'then' thông qua tính chất chain của promise:Lưu ý: chỉ cần 1 catch để bắt lỗi trong chuỗi then Ngoài ra, việc hiểu rõ tính chất chain của Promise cũng giúp chúng ta tránh khỏi tình trạng Promise hell, tương tự như Callback HellAsync: Được đặt trước 1 function để khai báo bất đồng bộ cho cho hàm. (VD: async function myFunc() {...}).
Từ khóa Await: Được sử dụng khi muốn tạm dừng việc thực hiện các hàm async (VD: Var result = await myAsyncCall())Await: Được sử dụng khi muốn tạm dừng việc thực hiện các hàm async (VD: Var result = await myAsyncCall())
Async/await giúp bạn chạy các promise 1 cách tuần tự: 1Async/await có thể sử dụng try catch như xử lí đồng bộ 2Vậy Async Await có thể thay thế hoàn toàn Promise hay không? Như mình đã đề cập ở định nghĩa, sử dụng Async/Await chính là đang sử dụng Promise ngầm, và Async/Await không thể nào thay thế được Promise. Và chúng ta hoàn toàn có thể sử dung cả hai cùng lúc, ví dụ chức năng Promise.all() với nhiệm vụ cho phép request nhiều trong cùng một thời gian. Hãy cùng xem đoạn code dưới đây để hiểu rõ hơn nhé: 3Như vậy, chúng ta phải chờ tổng cộng là 3000ms để thực hiện function sequence, điều này xảy ra do các promise được thực hiện một cách tuần tự: promise1 xong thì đến promise2, và cuối cùng là promise3. Vậy có cách nào để tăng tốc nó không?? Câu trả lời đó là Promise.all() Theo MDN: "Phương thức Promise.all(iterable) trả ra một Promise mới và promise mới này chỉ được kết thúc khi tất cả các promise trong iterable kết thúc hoặc có một promise nào đó xử lý thất bại. Kết quả của promise mới này là một mảng chứa kết quả của tất cả các promise theo đúng thứ tự hoặc kết quả lỗi của promise gây lỗi." "Phương thức Promise.all(iterable) trả ra một Promise mới và promise mới này chỉ được kết thúc khi tất cả các promise trong iterable kết thúc hoặc có một promise nào đó xử lý thất bại. Kết quả của promise mới này là một mảng chứa kết quả của tất cả các promise theo đúng thứ tự hoặc kết quả lỗi của promise gây lỗi." Lúc này hãy kết hợp nó với Promise.all() để thực hiện cả 3 cùng lúc nhé: 4Lúc này tổng thời gian để thực hiện hàm sequence chỉ mất 1500ms, tương đương với thời gian thực hiện lớn nhất của các promise đó là promise3. Điều này chính là nhờ Promise.all() đã giúp cho các request trên được thực hiện cùng lúc và kết thúc function khi request mất nhiều thời gian nhất được thực hiện xong. Thật thú vị phải không nào 5. Kết luận:Như vậy, chúng ta đã cùng tìm hiểu về xử lí đồng bộ và bất đồng bộ trong Javascript. Đây chính là những gì mình vừa đúc kết được trong quá trình tìm hiểu được về sync và async trong Javascript, nếu có sai sót hay thiếu sót, mong các anh chị, các bạn chỉ điểm. Xin cám ơn mọi người đã dành thời gian đọc. Tài liệu tham khảo: https://medium.com/swlh/synchronous-vs-asynchronous-programming-1bfef19f032c https://medium.com/@Rahulx1/understanding-event-loop-call-stack-event-job-queue-in-javascript-63dcd2c71ecd https://blog.bitsrc.io/understanding-asynchronous-javascript-the-event-loop-74cd408419ff https://codelearn.io/sharing/bat-dong-bo-trong-javascript-phan-1 https://codelearn.io/sharing/asyncawait-trong-javascript |