Tất tần tật về đồng bộ, bất đồng bộ trong javascript

1. Trước khi đến với khái niệm đồng bộ, bất đồng bộ chúng ta cùng đi phân tích một ví dụ sau:

Các bạn còn nhớ giáo sư Cù Trọng Xoay không nào, hãy chú ý một vài yếu tố sau:

  • Giáo sư Cù là một người thông thái, có thể giải quyết và trả lời hết mọi việc.
  • Muốn giáo sư Cù giải quyết công việc của bạn, cách duy nhất là nhắc điện thoại lên và gọi cho ông ấy.
  • Và đương nhiên trong cùng một thời điểm ông ấy chỉ giải quyết và trả lời câu hỏi của một người. Thế là công việc của bạn được giải quyết xong.

Ví dụ trên đã cho bạn có một hình dung về việc xử lý đồng bộ. Bạn các công việc được thực hiện trình tự, bạn nhắc điện thoại lên và gọi cho giáo sư Cù, sau khi lắng nghe câu hỏi của bạn, ông ấy lập tức trả lời và thế là cuộc trò chuyện kết thúc. Mới đẩu ta thấy mọi việc diễn ra rất trôi chảy thuận lợi. Nếu vậy thì vấn đề phát sinh ở chỗ nào.

Kết quả hình ảnh cho đồng bộ và bất đồng bộ trong javascript

Bạn biết rồi đó giáo sư Cù là một người thông thái, ông ấy có thể giải quyết tất cả các vấn đề từ nhỏ đến lớn, nên càng ngày càng có nhiều người muốn ông ấy giải đáp những thắc mắc của mình. Mà trong cùng một thời điểm ông ấy chỉ giải quyết vấn đề cho một người. Vậy số người gọi đến mà máy bận là rất nhiều. Vấn đề bắt đầu nảy sinh. Vậy lúc này ta không thể tiếp tục xử lý công việc theo cơ chế bất đồng bộ được nữa phải không nào.
Để giải quyết được vấn đề này, giáo sư đã nghĩ ra một phương pháp mới, thay vì gọi trực tiếp, giáo sư sẽ tiếp nhận các tin nhắn lại rồi trả lời thông qua tin nhắn:

  • Giáo sư đã thuê anh Xoáy để quản lí nhận tin nhắn.
  • Mỗi khi ai có vấn đề cần giải quyết chỉ cần nhắn tin và anh Xoáy sẽ tiếp nhận và sắp xếp những tin nhắn theo một trình tự rồi gửi đến cho giáo sư Cù.
  • Giáo sư Cù trả giải đáp thắc mắc của mọi người thông qua gửi message lại cho anh Cù, anh Cù sẽ tiếp nhận và gửi lại nó cho người gửi.

Do đó các tin nhắn được gửi sẽ không đợi nhau, đây chính là cách xử lý theo cơ chế bất đồng bộ.
Đến đây chắc hẳn đã có hình dung về đồng bộ và bất đồng bộ trong javascript. Để hiểu rõ hơn chúng ta hãy cùng đi đến khái niệm sau:

2. Khái niệm

  • Synchronous (đồng bộ) là một quy trình xử lý các công việc theo một thứ tự đã được lập sẵn. Công việc sau được bắt đầu thực hiện chỉ khi công việc thứ nhất hoàn thành. Trong lập trình, một chương trình đồng bộ là một chương trình được thực hiện theo từng câu lệnh từ trên xuống dưới, từ trái qua phải, câu lệnh sau được thực hiện chỉ khi câu lệnh trước đã hoàn thành. Đa số các ngôn ngữ biên dịch đều tuân theo quy tắc lập trình đồng bộ ví dụ C++, Java, …Vì thế chỉ cần biên dịch một câu lệnh sai là cả chương trình sẽ dừng lại và báo lỗi.
  • Ngược lại, với Asynchronous (bất đồng bộ), nhiều công việc có thể được thực hiện cùng lúc. Và nếu công việc thứ hai kết thúc trước, nó có thể sẽ cho ra kết quả trước cả câu lệnh thứ nhất. Vì thế, đôi khi kết quả của các câu lệnh sẽ không trả về đúng theo đúng thứ tự như trực quan của nó.

3. Ưu nhược điểm của xử lý đồng bộ, bất đồng bộ.

  • Lập trình đồng bộ có đặc điểm là chương trình chạy từ trên xuống dưới, nên khi có ở đâu thì chương trình sẽ dừng ngay tại đó. Vì thế có thể dễ dàng phát hiện ra lỗi.
  • Việc thực thi chương trình theo một trình tự cũng là một nhược điểm của lập trình bất đồng bộ. Khi các câu lệnh chờ nhau thực hiện, sẽ có những câu lệnh cần phải thao tác với các dữ liệu bên ngoài vì thế nó cần một khoảng thời gian để lấy dữ liệu trước khi hoạt động bình thường. Như thế thời gian chạy chương trình sẽ bằng tổng thời gian thực hiện xong các câu lệnh.
  • Đừng lo lắng, lập trình bất đồng bộ sẽ giúp bạn trong những trường hợp này, lập trình bất đồng bộ cho các câu lệnh thực hiện đồng thời mà không phải đợi nhau. Như thế quá tốt đúng không nào.
  • Tuy nhiên lập trình bất đồng bộ cũng có nhược điểm, đó là khi nếu câu lệnh thứ hai thực thi với thời gian ngắn hơn với câu lệnh thứ nhất thì câu lệnh ta sẽ nhận được kết quả của câu lệnh thứ hai trước. Vì thế có thể cho kết quả không theo trình tự như chủng ta mong muốn. Do vậy khi sử dụng lập trình bất đồng bộ ta phải có kĩ thuật để kiểm soát chúng.

4. Cách dùng

4.1 Callback Function

4.1.1 Khái niệm

Callback Function là gì?
Callback Function  là một function được truyền vào một function (gọi là F1) khác dưới dạng tham số, và được gọi trong function F1 đó.

Chúng ta cùng xem xét ví dụ sau:

Theo ví dụ như trên, ta khai báo hai function tên là hello() và myfunction(). Khi ta gọi thực thi myfunction(), phía trong function lại gọi tới một function là callback(). Như ta để ý thì callback cũng là tên tham số của function myfunction(), mà ở dòng thực thi ta truyền vào là hello.

4.1.2 Callback function hoạt động như thế nào?

Một function có thể truyền function khác như là một biến rồi return về function đó. Và chúng ta đã có định nghĩa của function callback dưới dang tham số, ta có thể thực thi bất kỳ lúc nào trong function chứa nó.

  1. Sử dụng anonymous functions

2. Sử dụng một callback function đã được đặt tên

Hình ảnh này chưa có thuộc tính alt; tên tệp của nó là image-20.png

Trong function myfunction() có tham số là callback. Function callback sẽ được thực thi khi gọi đến function myfunction().

3. Truyền tham số tới callback function
Chúng ta có thể truyền tham số vào callback function như bất kỳ function nào khác.

Kết quả:

4. Gọi nhiều callback function

Có thể gọi nhiều callback function dưới dạng tham số.

4.1.3 Ưu nhược điểm của callback function

Ưu điểm:
  • Callback function là một mô hình khá phổ biến nên rất dễ hiểu.
  • Rất dễ implement trong cách function của chúng ta.
Nhược điểm:
  • Callback lồng nhau sẽ tạo thành một kim tự tháp khét tiếng của sự diệt vong như được hiển thị ở trên, điều này có thể khiến bạn khó đọc khi bạn có nhiều mức lồng nhau. 
  • Bạn chỉ có thể truyền một callback cho một sự kiện nhất định, điều này có thể là một giới hạn lớn trong nhiều trường hợp.

4.2 Callback hell

Callback hell trong javascript là gì? Nó không phải là một khái niệm trừu tượng, nó là một tên gọi hoa mỹ của việc thực hiện quá nhiều callback lồng nhau.
Hãy xem ví dụ dưới đây:

Nhìn thật đáng sợ đúng không? Đừng lo lắng, mình sẽ chia sẻ những cách bị hạn chế callback hell trong javascript.

4.2.1 Thiết kế ứng dụng theo dạng module

Cũng giống như những ngôn ngữ lập trình khác, để giảm thiếu tối đa sự phức tạp của code là module hóa chúng.
Bạn đừng quên rằng Nodejs được xây dựng bởi hàng trăm hàng ngàn module. Nodejs sẽ không phổ biến đến thế nếu không sử dụng các module. Chính vì thế việc bạn sử dụng module để đơn giản hóa mã nguồn chính là đi đúng hướng với Nodejs đó.
Chúng ta hãy cùng xem một ví dụ sau:

Để ứng dụng khác gọi đến được thì bạn cần phải require nó.

4.2.2 Đặt tên cho callback trong javascript

Chúng ta hãy cùng xem ví dụ sau:

Nhìn vào đoạn code này sẽ khiến bạn mất vài giây để xem callback thực hiện điều gì và được gọi từ đâu.

Để khắc phục điều này, đơn giản bạn thêm một thao tác nhỏ là đặt tên cho callback. Nó sẽ giúp bạn dễ đọc code hơn, đặc biệt khi các callback lồng nhau nhiều hơn.

Và bây giờ thì đọc lại xem, việc đặt tên cho function sẽ mất ít thời gian để hiểu code hơn đúng không ạ.

4.2.2 Định nghĩa hàm trong javascript để tránh callback hell

Cũng như ví dụ ở trên nhưng hãy thử tách các function ra:

Bạn đã thấy dễ hiểu hơn chưa?

Mặc dù cách viết code đã giải quyết được phần nào vấn đề. Nhưng nó vẫn chưa phải là giải pháp tốt nhất. Nếu bạn đọc lại code mà không nhớ chính xác hàm đó làm gì, bạn sẽ phải trace lại code, mà thường thì code của hàm đó lại trôi tuột ở đâu đó. Rất mất thời gian.

Chúng ta còn có giải pháp tốt hơn, ngay phía bên dưới thôi!

4.3 Promise trong javascript

Promise trong javascript là gì?
Promise là một cơ chế trong javascript nó giúp bạn không bị rơi vào callback hell.
Hãy xem một ví dụ đơn giản sau:

Nếu dùng promise để viết lại:

Code nhìn ngắn gọn, đơn giản và dễ hiểu hơn rất nhiều có đúng không nào?
Để tạo ra một promise object thì bạn dùng class Promise có sẵn trong trình duyệt như sau:

Trong đó, executor là một hàm có hai tham số:

  • resolve là hàm sẽ được gọi khi promise hoàn thành
  • reject là hàm sẽ được gọi khi có lỗi xảy ra

Ví dụ:

Như vậy api.getUser() sẽ trả về một promise object. Chúng ta có thể truy xuất đến kết quả trả về bằng phương thức .then() như sau:

Phương thức .then(onSuccess, onError) nhận vào hai hàm: onSuccess được gọi khi promise hoàn thành và onError được gọi khi có lỗi xảy ra. Bên trong tham số onSuccess bạn có thể trả về một giá trị đồng bộ, chẳng hạn như giá trị số, chuỗi, nullundefined, array hay object; hoặc một promise object khác. Các giá trị bất đồng bộ sẽ được bọc bên trong một Promise, cho phép bạn kết nối (chaining) nhiều promises lại với nhau.

4.4 Async/Await

Kể từ phiên bản ES7, Javascript có một khái niệm mới là Async/Await. Khi sử dụng hàm async, code của bạn sẽ trông giống như đồng bộ như thực chất là bất đồng bộ. Thế mới hay!

Ví dụ đoạn code sau:

Ở đoạn code trên, hàm db.user.byId(id) sẽ trả về một Promises, và lẽ ra khi hàm này được sử dụng thì kết quả sẽ trả về trong hàm .then()

Tuy nhiên, với từ khóa await, bạn sẽ lấy trực tiếp kết quả trả về.

Kết luận

Trên đây là một số những kiến thức cơ bản về đồng bộ, bất đồng bộ trong javascript. Bạn cần nắm rõ khái niệm và cách sử dụng cho hiệu quả. Hiểu được khái niệm và cách dùng của callback function, promise và async/ await. Callback hell là gì và những cách để hạn chế callback hell.