Hỏi - đáp Nơi cung cấp thông tin nghề nghiệp và giải đáp những thắc mắc thường gặp của bạn

Xử lý bất đồng bộ trong JavaScript

I. Đồng bộ và bất đồng bộ

1) 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. Quá trình đồng bộ là một quá trình rất phổ biến trong thực tế. Ví dụ dây chuyền sản xuất sản phẩm trong các nhà máy, hay lộ trình học tập của một học sinh từ lớp 1 đến lớp 12, … 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ó.

2) So sánh ưu nhược điểm của lập trình đồng bộ và bất đồng bộ

  • Lập trình đồng bộ có ưu điểm là chương trình sẽ chạy theo đúng thứ tự từ trên xuống, và sẽ phát dừng lại ngay khi gặp một câu lệnh lỗi. Điều này sẽ khiến chương trình dễ kiểm soát và dễ phát hiện ra lỗi hơn.
  • Lập trình đồng bộ cũng có một nhược điểm là hiệu suất chương trình sẽ chậm. Có rất nhiều câu lệnh cần phải thao tác với các dữ liệu bên ngoài nên nó cần có một thời gian chờ để nhận được dữ liệu trước khi hoạt động bình thường. Như thế thời gian thực hiện của chương trình sẽ bằng tổng thời gian chờ của các câu lệnh và thời gian hoạt động bình thường của các câu lệnh đó. Ở một số ngôn ngữ đồng bộ đa luồng, vấn đề này được xử lý bằng cách bổ sung một luồng để thực hiện các câu lệnh khác. Luồng ban đầu sẽ chờ kết quả của luồng bổ sung, sau đó hai luồng sẽ đồng bộ hóa để kết hợp lại kết quả của chúng.
  • Lập trình không đồng bộ có cách khác để giải quyết vấn đề trên. Chúng cho phép các hành động được thực hiện cùng lúc. Do đó, sẽ tối ưu được thời gian chờ của các câu lệnh vì các câu lệnh sẽ được “chờ cùng nhau”. Ở ví dụ sau, câu lệnh 1 và 2 đã “cùng nhau chờ” 0,4s, do đó thời gian thực hiện chương trình giảm được 0,4s.

    Ở hình sau, đường màu đỏ thể hiện thời gian chờ của một câu lệnh, đường màu xanh dày thể hiện thời gian thực hiện bình thường của lệnh đó.

      • Tuy nhiên, lập trình không đồng bộ cũng có nhược điểm. Do các câu lệnh không đồng bộ có thể sẽ không được thực hiện theo đúng thứ tự từ trên xuống theo quy trình nên đòi hỏi chúng ta cần có các kỹ thuật để kiểm soát chúng. Ví dụ cụ thể ở đoạn mã sau:

Câu lệnh thứ hai trả về kết quả trước câu lệnh thứ nhất

II. Bất đồng bộ trong JavaScript

Javascript là ngôn ngữ lập trình bất đồng bộ và chỉ chạy trên một luồng. Sự bất đồng bộ trong javascript xuất hiện khi nó thao tác với các WebAPI (ajax, setTimeout(), … ). Khi một câu lệnh thao tác với WebAPI, nó sẽ mất một khoảng thời gian để chờ các dữ liệu trả về từ WebAPI, do đó ở trong luồng chính của javascript, nó sẽ ở trong trạng thái chờ. Tuy nhiên chương trình sẽ không bỏ trống khoảng thời gian chờ đó, chương trình sẽ tiếp tục thực hiện các câu lệnh tiếp theo. Đó là lý do Javascript là ngôn ngữ bất đồng bộ. Sau đây chúng ta sẽ tìm hiểu kĩ hơn về cách javascript hoạt động với các trường hợp bất đồng bộ.

Một câu lệnh trong javascript khi được thực hiện nó phải trải qua sự kiểm soát các các đối tượng: Timer, Message Queue, Event Loop, CallStack. Đầu tiên, nếu một câu lệnh được gọi thao tác với WebAPI, nó sẽ được chuyển đến hàng đợi Timer. Sau khi hết thời gian chờ nó sẽ được chuyển đến Message Queue. Call Stack là ngăn xếp rất quen thuộc trong lập trình. Khi một hàm được gọi, hàm đó được thêm vào ngăn xếp và hàm đó sẽ được lấy ra khỏi ngăn xếp khi hàm đó thực thiện xong. Event Loop sẽ kiểm tra khi nào trong Call Stack trống thì sẽ chuyển câu lệnh trong Message Queue vào trong Call Stack.

III. Xử lý bất đồng bộ trong JavaScript

Để làm cho các câu lệnh thực hiện theo đúng thứ tự của nó, chúng ta có 3 phương án giải quyết phổ biến : Call Back, Promise, Asyn/Await

Tham khảo thêm: Xử lý Bất đồng bộ bằng Callback, Promise, Async Await hay Observable?

1) Call Back

Call Back là một hàm được truyền vào một hàm khác với tư cách như một tham số của hàm đó. Ví dụ như

Ở đoạn mã trên, chúng ta thấy rằng, hàm luocGa và nuongGa được dùng như tham số trong hàm nauGa Với Javascript, một ngôn ngữ hướng sự kiện, call back được sử dụng rất nhiều khi xử lý các sự kiện, ví dụ như

Chúng ta có thể áp dụng call back để đồng bộ hóa các đoạn mã không đồng bộ. Ví dụ như ở đoạn mã trên. Nấu nước sôi cần một khoảng thời gian chờ nước sôi, chúng ta không phải làm gì. Ta có thể biểu diễn thời gian chờ này bằng hàm setTimeout() trong javascript.