Asynchronous và Synchronous
Bất đồng bộ và đồng bộ trong Javascript
Javascript là ngôn ngữ đơn luồng (single-threaded), điều này có nghĩa là nó chỉ có thể xử lý một câu lệnh tại một thời điểm.
Single-threaded language đơn giản hoá việc viết code, khi bạn không cần phải quan tâm đến concurrency, tuy nhiên khi bạn thực hiện những tác vụ tiêu tốn thời gian dài để hoàn thành (chẳng hạn như network access) thì luồng chính (main thread) sẽ bị block.
Hãy thử tưởng tượng bạn truy cập 1 trang web và nó request đến 1 API và API đó tiêu tốn 2 phút để phản hồi, nếu với cơ chế Single threaded thì chắc hẳn chúng ta phải chờ 2 phút thì các đoạn lệnh khác mới được thực hiện sao ?
Javascript không ngu ngốc như vậy, nó có 1 thứ cực kì hay ho mà chúng ta gọi là cơ chế bất đồng bộ (asynchronous)
Javascript hoạt động đồng bộ như thế nào ?
Trước khi tìm hiểu về cơ chế bất đồng bộ - Asynchronous ta sẽ tìm hiểu về cơ chế đồng bộ Synchronous
Để hiểu được đoạn code trên được thực thi như thế nào bên trong Javascript Engine, chúng ta cần hiểu những khái niệm về execution context và call stack (execution stack)
Đọạn code phía trên sẽ được JavaScript engine thực thi như minh hoạ dưới đây
Việc thực thi đoạn code trên được giải thích như sau:
Đầu tiên thì global execution context sẽ được tạo ra (đại diện bởi
main()
) và được đẩy vào top của call stack.Tiếp đến nó thực thi đến lời gọi hàm
first()
, thì function execution context được tạo khi thực thi hàmfirst()
sẽ được đẩy tiếp vào top của call stack.kế tiếp
console.log(‘Hi there!’)
được push vào top của call stack, sau khi nó được thực thi xong, nó được lấy ra khỏi call stack. Khi Javascript engine thực thi lời gọi hàmsecond()
nó push function execution context củasecond()
lên top của call stack.console.log(‘Hello there!’)
được push vào top của call stack và được lấy ra khỏi call stack khi nó finish, hàmsecond()
finish và nó được lấy ra khỏi call stack.Chương trình finish và
main()
được lấy ra khỏi call stack.
Javascript hoạt động bất đồng bộ như thế nào ?
Khi bạn nghe nói Javascript là một ngôn ngữ bất đồng bộ, nó có nghĩa là bạn có thể điều khiển để chương trình hoạt động như là bất đồng bộ nhưng thực ra về bản chất, các hoạt động vẫn diễn ra tuần tự chứ không phải đồng thời. Nghe có vẻ khó hiểu nhưng đó là vì các ngôn ngữ lập trình như C/C++, Java,… để thực thi bất đồng bộ sẽ sử dụng cơ chế đa luồng tức là có nhiều luồng thực thi đồng thời, mỗi luồng thực hiện các công việc khác nhau. Nhưng Javascript lại có nền tảng là đơn luồng, nên không thể thực thi đồng thời, bất đồng bộ theo cách trên. Vậy nên để xử lý bất đồng bộ, Javascript đã phát triển một sô cách đó là: Callback, Promise và Async/await.
Blocking và Non-Blocking là gì ?
Blocking và Non-Blocking trong lập trình chủ yếu được đề cập khi muốn miêu tả về cách một chương trình thực hiện các dòng lệnh của nó. Chúng ta có thể hiểu một cách đơn giản, nếu chương trình được thực hiện theo mô hình Blocking có nghĩa là các dòng lệnh được thực hiện một cách tuần tự. Khi một dòng lệnh ở phía trước chưa được hoàn thành thì các dòng lệnh phía sau sẽ chưa được thực hiện và phải đợi khi mà thao tác phía trước hoàn tất, và nếu như các dòng lệnh trước là các thao tác cần nhiều thời gian xử lý như liên quan đến IO (input/output) hay mạng (networking) thì bản thân nó sẽ trở thành vật cản trở ( blocker ) cho các lệnh xử lý phía sau mặc dù theo logic thì có những việc ở phía sau ta có thể xử lý được luôn mà không cần phải đợi vì chúng không có liên quan gì đến nhau.
Mô hình blocking tồn tại từ lịch sử, khi mà máy tính chỉ có thể xử lý đơn nhiệm trên một lõi (core) của bộ vi xử lý (chip). Nhưng theo thời gian, công nghệ ngày một trưởng thành với những thành tựu về phần cứng, máy tính giờ có thể làm nhiều việc cùng một lúc thì người ta cần phải suy nghĩ đến việc làm sao tận dụng được tối đa tài nguyên xử lý của máy tính và tránh lãng phí nó. Từ đó mà bất cứ chỗ nào có phần xử lý Blocking không cần thiết, người ta cần thay vào một giải pháp xử dụng tài nguyên khôn ngoan hơn, đó là Non-Blocking.
Trong mô hình Non-Blocking, các dòng lệnh không nhất thiết phải lúc nào cũng phải thực hiện một cách tuần tự (sequential) và đồng bộ (synchronous) với nhau. Ở mô hình này nếu như về mặt logic dòng lệnh phía sau không phụ thuộc vào kết quả của dòng lệnh phía trước, thì nó cũng có thể hoàn toàn được thực hiện ngay sau khi dòng lệnh phía trước được gọi mà không cần đợi cho tới khi kết quả được sinh ra. Những dòng lệnh phía trước miêu tả ở trên còn có thể gọi là được thực hiện theo cách không đồng bộ (Asynchronous), và đi theo mỗi dòng lệnh thường có một callback
(lời gọi lại) là đoạn mã sẽ được thực hiện ngay sau khi có kết quả trả về từ dòng lệnh không đồng bộ. Để thực hiện mô hình Non-Blocking, người ta có những cách để thực hiện khác nhau, nhưng về cơ bản vẫn dựa vào việc dùng nhiều Thread (luồng) khác nhau trong cùng một Process (tiến trình), hay thậm chí nhiều Process khác nhau (inter-process communication – IPC) để thực hiện. Và mẫu thiết kết (design pattern) tên là event-loop là một trong những mẫu thiết kế nổi tiếng để thực hiện cơ chế Non-Blocking
The Event Loop
Nhiệm vu của Event Loop sẽ quan sát Call Stack để xác định xem Call Stack hiện có đang empty hay không. Nếu Call Stack empty thì sẽ quan sát Message Queue để xem hiện có callback nào đang chờ để được thực thi hay không
Các phương pháp xử lý bất đồng bộ
Sử dụng callback để xử lý bất đồng bộ
Khi hành động bắt đầu, rồi khi nó kết thúc, hàm callback sẽ được gọi ngay sau đó.
Ở đây, hàm doAsync là một hàm bất đồng bộ với 2 hàm callback là: onSuccess và onError. Khi request trên thành công thì hàm onSuccess sẽ được gọi, ngược lại hàm onError sẽ được gọi.
doAsync là một hàm bất đồng bộ vì nó chứa hàm callback
Sử dụng Promise để xử lý bất đồng bộ
Cú pháp cơ bản của Promise
Trong đó, hàm được truyền vào new Promise gọi là executor.
Ban đầu, Promise có state là pending và kết quả value là undefined. Khi executor kết thúc công việc, nó sẽ gọi đến 1 trong 2 hàm được truyền vào:
resolve(value): để xác định rằng công việc đã thực hiện thành công
state chuyển thành fulfilled
kết quả là value
reject(error): để xác định rằng đã có lỗi xảy ra
state chuyển thành rejected
kết quả là error
Khi sử dụng Promise, ví dụ phía trên sẽ trở thành:
Sử dụng Async/Await để xử lý bất đồng bộ
Async/await là một cú pháp đặc biệt giúp bạn làm việc với Promise dễ dàng hơn. Khi sử dụng async/await, cấu trúc chương trình xử lý bất đồng bộ sẽ giống với chương trình xử lý đồng bộ hơn.
Last updated
Was this helpful?