+1

Asynchronous programing: callback, promise, async await,...

Mayfest2023

1. Synchronous là gì?

  • Xử lý đồng bộ
  • Cách lập trình mà hoạt động của chương trình thực hiện tuần tự => Tức là thực hiện xong bước 1 mới đến bước 2, các đoạn mã được thực thi theo thứ tự từ trên xuống dưới.

1.1 Ưu điểm của lập trình đồng bộ:

  • Các hoạt động xảy ra tuần tự nên có thể dễ quản lý, dễ debug và phát hiện vấn đề khi xảy ra lỗi.
  • Hoạt động có trình tự nên ít khi để xảy ra lỗi

1.2 Nhược điểm của lập trình đồng bộ:

  • Sinh ra trạng thái chờ do phải đợi một task vụ nào đó thực hiện xong. Đôi khi việc này gây ảnh hưởng đến hệ thống.
  • Ví dụ làm việc với api lấy danh sách users từ database hay từ file. Trong trường hợp data quá lớn thì việc api này sẽ trả về rất lâu(thời gian chờ lâu) => chương trình sẽ bị đứng (block UI)

2. Asynchronous là gì?

image.png

  • Lập trình bất đồng bộ
  • cho phép các tác vụ thực hiện không đồng thời
  • không phụ thuộc vào việc thực hiện các tác vụ khác.

2.1 Sử dụng khi nào?

Khi thực hiện những tác vụ cần nhiều thời gian để hoàn thành, hoặc phụ thuộc vào hàm khác thực thi xong:

  • Tạo network request 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 đọc/ghi file bằng showOpenFilePicker()

2.2 Ư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)

image.png https://codesandbox.io/s/advantage-asynchronous-7q50ih?file=/src/index.js

Chương trình không đợi dữ liệu để tiếp tục thực hiện, do đó có thể thực hiện các tác vụ khác hoặc phản hồi tương tác của người dùng trong khi chờ hoạt động bất đồng bộ hoàn tất.

2.3 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

2.4 Cách hoạt động

2.4.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. image.png

2.4.2. Asynchronous

image.png 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

3. Callback

  • Là 1 function
  • Được truyền vào như là đối số của 1 function khác
  • Được gọi lại sau khi hàm đó thực hiện

https://codesandbox.io/s/bold-glade-3phqcv?file=/src/index.js

image.png 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ề.

3.1 Ưu điểm:

image.png

https://codesandbox.io/s/fetchdata-callback-32us1j?file=/src/index.js

  • Xử lý lỗi: Cung cấp cơ chế xử lý lỗi trong hoạt động bất đồng bộ. Theo quy ước, đối số đầu tiên của callback thường được dành riêng cho tham số lỗi. Điều này cho phép các hoạt động bất đồng bộ chuyển bất kỳ lỗi gặp phải để xử lý thích hợp.

  • 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ự.

  • Kiểm soát luồng bất đồng bộ: 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.

3.2 Nhược điểm:

image.png

https://codesandbox.io/s/callback-hell-fu55wy?file=/src/index.js

  • Callback Hell- Việc gọi lại nhiều lần có thể dẫn đến callback hell => khi các hàm lồng vào nhau hoặc phụ thuộc hàm khá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.

4. Promise

  • Thuộc tính JS để xử lí các hoạt động bất đồng bộ một cách có cấu trúc và dễ quản lí hơn
  • Cung cấp cú pháp rõ ràng hơn và cho phép xử lý lỗi tốt hơn và xâu chuỗi các tác vụ không đồng bộ.

Nhận vào 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ối
  • finally: 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

image.png 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

4.1 Ưu điểm:

  • Giúp mã code dễ đọc và dễ bảo trì : giúp viết mã bất đồ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: Promise 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ó.

4.2 Nhược điểm

  • Callback hell: Mặc dù giúp giảm thiểu callback hell ở một mức độ nào đó, nhưng việc xâu chuỗi nhiều Promise vẫn có thể dẫn đến cấu trúc mã lồng nhau và giảm khả năng đọc mã. Điều này có thể xảy ra khi xử lý các hoạt động bất đồng bộ phức tạp hoặc khi thực hiện nhiều tác vụ phụ thuộc.

image.png https://codesandbox.io/s/callback-hell-promise-l1m8xx

  • Không thay đổi: khi một Promise được bắt đầu, kết quả trả ra là cố định. Đây có thể là một hạn chế khi xử lý các tác vụ bất đồng bộ chạy dài hoặc không cần thiết, vì chúng không thể bị dừng hoặc gián đoạn.

image.png https://codesandbox.io/s/cancel-promise-ul22fh?file=/src/index.js

3. Async/ Await

  • Một tính năng được giới thiệu trong ECMAScript 2017(ES8)

  • Cung cấp cách viết mã ngắn gọn và dễ đọc hơn so với callback hoặc promise.

  • Async khai báo một hàm bất đồng bộ => luôn trả về một giá trị

  • Await dùng để đợi Promise thực hiện xong

  • Handing Error: dùng khối try/ catch để trả về lỗi trong async/await

image.png 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

  • Dễ đọc và đơn giản hóa: async/await cung cấp một cách viết mã cho phép viết mã bất đồng bộ giống với mã đồng bộ truyền thống, giúp dễ đọc, dễ hiểu và suy luận hơn. image.png https://codesandbox.io/s/simplicity-await-zw6fx8?file=/src/index.js

  • 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,

  • Xâu chuỗi và kết hợp: async/await cho phép bạn kết hợp và kết hợp nhiều hoạt động bất đồng bộ theo cách tuần tự và trực quan. Điều này giúp tránh callback hell và cho phép mã mô-đun và dễ bảo trì hơn.

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.

  • Xử lí lỗi với nhiều promise: Khi xử lí nhiều promise đồng thời => xử lí lỗi trở nên phức tạp hơn. Nếu có nhiều promise mà muốn đợi song song và xử lí độc lập => sử dụng Promise.all()

image.png

https://codesandbox.io/s/promise-all-4pxg9x?file=/src/index.js


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí