Asynchronous là gì?
- một kỹ thuật lập trình
- cho phép các tác vụ (tasks) có thể thực hiện đồng thời (concurrently)
- không phụ thuộc vào việc thực hiện các tác vụ khác.
- xử lý kết quả khi nào chúng hoàn thành.
Sử dụng khi nào?
Nhiều hàm cung cấp bởi trình duyệt, cần thời gian để lấy dữ liệu từ hàm khác, vì thế nó là asynchronous. Ví dụ:
- Tạo requests HTTP bằng
fetch()
- Truy cập camera/ microphone người dùng bằng
getUserMedia()
- Yêu cầu người dùng lựa chọn file bằng
showOpenFilePicker()
Trong lập trình đồng bộ (synchronous programming), chương trình sẽ tiếp tục chạy sau khi tác vụ hiện tại hoàn thành. Trong khi đó, trong lập trình bất đồng bộ (asynchronous programming), chương trình có thể tiếp tục chạy và thực hiện các tác vụ khác trong khi tác vụ đang chờ xử lý hoàn thành.
Ưu điểm:
- Giảm thiểu thời gian chờ đợi
- Tăng khả năng phản hồi của hệ thống
- Sử dụng cho hệ thống đa luồng => Tăng hiệu năng hệ thống
- Tiết kiệm tài nguyên (giảm thiểu các thread cần thiết để thực hiện)
Nhược điểm:
- Do chạy đồng thời nên khi có lỗi rất khó để debug
- Khó khăn tron quản lý và kiểm soát các tác vụ
- Code phức tạp hơn
Tóm lại, Asynchronous có nhiều ưu điểm giúp cải thiện hiệu suất và khả năng phản hồi của hệ thống, tuy nhiên cũng có những nhược điểm cần được quản lý và giải quyết để tối ưu hóa kỹ thuật này.
Asynchronous programming thường được sử dụng trong các ứng dụng web và các hệ thống đa luồng (multi-threaded systems) để giảm thiểu thời gian chờ đợi của người dùng và tăng khả năng phản hồi (responsiveness) của hệ thống.
Cách hoạt động
1. Synchronous
Như chúng ta thấy, khi sử dụng đồng bộ, chương trình sẽ thực hiện từng tác vụ một, đợi cho đến khi tác vụ hiện tại hoàn thành trước khi thực hiện tác vụ tiếp theo.
2. Asynchronous
https://codesandbox.io/s/priceless-marco-c8wgzg?file=/src/index.js
Step 1 console.log("AAAAAAAAA")
, console.log("0")
, console.log("1")
được push vào stack sau đó pop ra ngoài
Step 2 setTimeout()
được push vào stack nhưng chưa được xử lý, sau 3000ms, function này sẽ được gửi sang Callback Queue để xử lý
Step 3 console.log("4")
được push vào stack sau đó pop ra ngoài
Step 4 Sau 3000ms, Even Loop kiểm tra Callback Queue, lúc này callback trống nên nó push vào callback và thực hiện xử lí => console.log("2") được pop ra ngoài
Như chúng ta thấy, khi sử dụng bất đồng bộ, chương trình sẽ không đợi cho tác vụ hiện tại hoàn thành trước khi thực hiện tác vụ tiếp theo. Trong ví dụ này, chúng ta sử dụng hàm setTimeout()
để giả lập việc thực hiện một tác vụ lâu dài. Khi hàm này được gọi, nó sẽ chờ 3 giây trước khi thực hiện tác vụ được đưa vào callback function. Trong khi đợi thời gian chờ này, chương trình sẽ tiếp tục thực hiện các tác vụ khác. Khi tác vụ lâu dài hoàn thành, callback function được đưa vào hàm setTimeout() sẽ được gọi để xử lý kết quả.
Tuy nhiên, xử lí bất đồng bộ khiến khó kiểm soát code, để làm câu lệnh thực hiện đúng theo thứ tự của nó => có 3 phương án chính:
- Callback
- Promise
- Async/Await
1. Callback
Trong JS, callback là hàm được truyền vào một hàm khác như đối số và sẽ được gọi sau khi hàm gốc thực hiện xong
https://codesandbox.io/s/bold-glade-3phqcv?file=/src/index.js
Trong ví dụ này, ta khai báo một hàm cal nhận vào 3 tham số: a, b và callback. Hàm cal
tính tổng của a và b, và truyền kết quả vào callback
. Sau đó, ta khai báo một hàm logResult
nhận vào một tham số result
. Hàm logResult
được truyền vào làm đối số thứ 3 khi gọi hàm cal
. Khi hàm cal
tính xong kết quả, nó gọi hàm callback với tham số là kết quả tính được. Trong trường hợp này, hàm logResult
được gọi và in kết quả ra console. Với callback, ta có thể xử lý các tác vụ bất đồng bộ một cách dễ dàng. Ví dụ, ta có thể thực hiện một yêu cầu HTTP để lấy dữ liệu từ server, và truyền callback function để xử lý dữ liệu khi yêu cầu trả về.
Ưu điểm:
-
Hoạt động không đồng bộ: Hàm gọi lại thường được sử dụng để xử lý các hoạt động không đồng bộ, chẳng hạn như tạo yêu cầu API hoặc đọc tệp, vì chúng cho phép mã tiếp tục chạy trong khi hoạt động hoàn tất ở chế độ nền.
-
Khả năng sử dụng lại mã: Các hàm gọi lại có thể làm cho mã của bạn trở nên mô-đun hơn và có thể tái sử dụng, bởi vì bạn có thể chuyển cùng một hàm cho nhiều hàm thực hiện các tác vụ tương tự.
-
Đơn giản hóa luồng điều khiển: Các chức năng gọi lại có thể đơn giản hóa luồng điều khiển mã của bạn, bởi vì bạn có thể chuyển một chức năng này sang một chức năng khác sẽ chỉ được thực thi khi chức năng đầu tiên hoàn thành.
Nhược điểm:
- Callback Hell- Nhược điểm lớn nhất của callback. Về cơ bản là lưới callback lồng nhau khi mỗi callback phụ thuộc vào callback trước đó.
- Gây khó hiểu cho việc đọc code và bảo trì
- Các vấn đề về hiệu suất: Các chức năng gọi lại cũng có thể gây ra các vấn đề về hiệu suất vì chúng có thể tạo ra nhiều chi phí hoạt động và làm chậm mã của bạn nếu bạn có quá nhiều lệnh gọi lại chạy cùng một lúc.
2. Promise
Là một đối tượng trong JS xử lí tác vụ bất đồng bộ đồng thời tránh callback hell
https://codesandbox.io/s/late-glade-ll0t9u?file=/src/index.js
Có 3 trạng thái chính:
- Pending: trạng thái ban đầu, chưa thực hiện xong
- Fulfilled: Promise đã thực hiện xong và trả về kết quả mong đợi
- Reject: Promise đã thực hiện xong nhưng ko thành công, báo lỗi
Promise nhận callback gồm 2 tham số:
- 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.
Các phương thức để xử lý kết quả của Promise:
then()
: xử lí giá trị của promise khi thực hiện thành công.catch()
: dùng để xử lí lỗi của Promise khi bị từ chốifinally
: thực hiện 1 hành động nào đó khi hoàn thành Promise, bất kể Promise thành công hay bị từ chối
Ưu điểm:
-
Mã không đồng bộ được đơn giản hóa: giúp viết mã không đồng bộ dễ dàng hơn bằng cách cung cấp một cách có cấu trúc hơn để xử lý kết quả của thao tác không đồng bộ.
-
Xử lý lỗi tốt hơn: cung cấp cách xử lý lỗi dễ đọc và dễ hiểu hơn các khối try/catch.
-
Khả năng đọc mã được cải thiện: Lời hứa có thể làm cho mã của bạn dễ đọc hơn và dễ hiểu hơn bằng cách cung cấp sự tách biệt rõ ràng giữa mã bắt đầu hoạt động không đồng bộ và mã xử lý kết quả của nó.
Nhược điểm
-
Độ phức tạp: phức tạp hơn, đặc biệt là khi xử lý các hoạt động không đồng bộ lồng nhau hoặc phụ thuộc.
-
Hỗ trợ trình duyệt hạn chế: có thể không được hỗ trợ bởi các trình duyệt cũ hơn, yêu cầu sử dụng polyfill hoặc bộ chuyển mã.
3. Async/ Await
Trong JavaScript, async/await là một tính năng cú pháp được giới thiệu trong ECMAScript 2017 cung cấp cách viết mã không đồng bộ ngắn gọn và dễ đọc hơn so với callback hoặc promise truyền thống.
-
Async khai báo một hàm bất đồng bộ => luôn trả về một giá trị
-
Await dùng để chờ một Promise
-
Await chỉ sử dụng trong khối Async => làm cho đợi đến khi promise trả về
https://codesandbox.io/s/cool-snowflake-8gslw6?file=/src/index.js
fetchData()
để tải dữ liệu từ API bằng cách sử dụng hàm fetch()
. Chúng ta đã sử dụng từ khóa await
để đợi kết quả của hàm fetch()
và sau đó sử dụng await
một lần nữa để đợi kết quả từ hàm res.json()
.
Nếu thành công, dữ liệu sẽ được in ra bằng cách sử dụng console.log()
.
Nếu có lỗi, nó sẽ được in ra bằng cách sử dụng console.log()
.
Sử dụng async/await sẽ giúp mã trở nên dễ đọc và dễ hiểu hơn, đồng thời giúp bạn tránh được việc sử dụng các callback lồng nhau.
Ưu điểm
-
Mã không đồng bộ được đơn giản hóa: async/await cung cấp một cách viết mã không đồng bộ đơn giản và có cấu trúc hơn, ít lệnh gọi lại lồng nhau hơn.
-
Xử lý lỗi: async/await giúp xử lý lỗi trong mã không đồng bộ dễ dàng hơn bằng cách sử dụng các khối try/catch,
-
Khả năng đọc: async/await có thể làm cho mã dễ đọc hơn và dễ hiểu hơn, với các hàm async trông tương tự như các hàm đồng bộ
-
Gỡ lỗi: async/await có thể giúp gỡ lỗi mã của bạn dễ dàng hơn vì bạn có thể sử dụng các tính năng của trình gỡ lỗi tương tự như đối với mã đồng bộ.
Nhược điểm:
-
Hỗ trợ trình duyệt hạn chế: async/await là một tính năng tương đối mới được giới thiệu trong ECMAScript 2017 (ES8) và có thể không được hỗ trợ trong các trình duyệt cũ hơn.
-
Cần chuyển mã: async/await có thể yêu cầu chuyển mã để hoạt động trong một số phiên bản cũ hơn của Node.js hoặc các trình duyệt cũ hơn.
-
Độ phức tạp: async/await có thể phức tạp hơn để triển khai so với Promise hoặc các chức năng gọi lại truyền thống, đặc biệt là khi xử lý nhiều hoạt động không đồng bộ hoặc xử lý lỗi.