Hiểu về đồng bộ và không đồng bộ trong JavaScript

Tổng hợp và dịch từ 2 phần chuỗi bài viết Understanding Synchronous and Asynchronous in JavaScript phần 1, phần 2.

Đồng bộ vào không đồng bộ là những khái niệm rất khó hiểu trong JavaScript, nhất là cho người mới bắt đầu.

Có thể hiểu một cách đơn giản là hai hay nhiều việc được gọi là đồng bộ khi chúng xảy ra trong cùng một lần, ngược lại là không đồng bộ.

Mặc dù định nghĩa trên trông có vẻ rất đơn giản nhưng thực sự thì nó phức tạp hơn. Chúng ta cần phải xác định chính xác những gì sẽ được đồng bộ và những gì không đồng bộ.

JavaScript - ngôn ngữ lập trình không đồng bộ.

Bạn nên nhớ rằng hàm trong JavaScript không bao giờ được gọi trực tiếp mà sẽ được gọi thông qua các thông điệp.

JavaScript sử dụng một hàng đợi các thông điệp (message queue) - nơi giữ các thông điệp (hoặc là sự kiện) đến. Một vòng lặp sự kiện (event loop - như là một người điều phối message) tuần tự gửi các message đến một lời gọi stack. Stack đó là nơi sẽ lưu trữ các chức năng tương ứng với các thông điệp và được xếp chồng lên nhau để thực hiện.

Stack lưu frame của chức năng ban đầu được gọi đến, sau đó bất kỳ frame cho các chức năng khác (vẫn của cùng một thông điệp) được gọi qua các cuộc gọi lồng nhau sẽ được đẩy lên lưu bên trên nó. Hãy xem hình về việc gọi stack, vòng lặp sự kiện (event-loop) và hàng đợi thông điệp bên dưới.

Khi một thông điệp được gửi tới hàng đợi :

  • Nó sẽ chờ cho đến khi stack được giải phóng hết các chức năng tương ứng với thông điệp trước đó.

  • Khi stack đã được giải phóng hết, vòng lặp sự kiện sẽ sắp xếp lại các thông điệp trước đó.

  • Thêm các chức năng tương ứng với thông điệp hiện tại vào stack.

Theo dõi đoạn code bên dưới và cách làm việc khi gọi stack thì bạn sẽ hiểu rõ hơn :

function foo(){}

function bar(){
  foo();
}

function baz(){
  bar();
}

baz();

Ta đang chạy hàm baz(), với mỗi thông điệp được thêm vào hàng đợi, vòng lặp sự kiện sẽ nhận thấy và thêm các chức năng tương ứng vào stack theo thứ tự baz() -> bar() -> foo() để thực hiện.

Khi thêm hết các chức năng vào stack thì ta bắt đầu thực hiện theo thứ tự ngược lại : foo() -> bar() -> baz(). Cứ hoàn thành một chức năng nào thì chức năng đó sẽ bị xóa khỏi stack. Trong khi đó thì thông điệp sẽ vẫn chờ ở trong hàng đợi cho đến khi nào baz() xuất hiện trên stack.

Tuy nhiên, không phải mọi thứ trong JavaScript đều là ko đồng bộ. Phần bên dưới của bài viết này sẽ đề cập đến setTimeout()AJAX.

setTimeout()

Nếu bạn muốn function của bạn sẽ được thực hiện sau một khoảng thời gian nhất định thì bạn hãy sử dụng phương thức setTimeout với cú pháp như sau :

setTimeout(function, delay-time, arg...)
  • function là hàm bạn cần delay thực hiện.

  • delay-time đơn vị là mili giây.

  • arg là bất kỳ đối số nào mà function của bạn cần dùng đến.

Cùng xem ví dụ in ra dòng hey sau 3 giây.

setTimeout( function() { console.log("hey") }, 3000 );

Khi setTimeout() bắt đầu, thay vì phải khóa lời gọi stack cho đến khi hết thời gian chờ, ta kích hoạt một timer (giống như là một người đếm giờ).

Khi timer hết hạn, một thông điệp mới sẽ được tham gia vào hàng đợi. Vòng lặp sự kiện thấy và đẩy vào stack khi stack đã được giải phóng hết bởi thông điệp trước đó. Code chạy là không đồng bộ.

AJAX

AJAX (Asynchronous JavaScript and XML) là một khái niệm mà chúng ta sử dụng API XMLHttpRequest (XHR) để tạo request lên server và xử lý các response trả về.

Khi browser tạo request lên server mà ko dùng XMLHttpRequest thì trang sẽ được làm mới và giao diện sẽ được load lại. Khi các request và response được xử lý bởi API XHR thì giao diện người dùng sẽ không bị ảnh hưởng.

Mục đích cơ bản là tạo ra những request mà không cần phải load lại trang. Đâu là không đồng bộ ở đây ? Chỉ sử dụng mã XHR không có nghĩa nó là AJAX, bởi vì API XHR có thể làm việc với cả 2 cách thức : đồng bộ và không đồng bộ.

  • Mặc định thì XHR được thiết kế để làm việc không đồng bộ, tức là khi function sử dụng XHR để tạo một request thì nó sẽ trả về luôn mà không chờ đợi response.

  • Nếu được cấu hình để làm việc đồng bộ, function sẽ chờ cho đến khi nhận và xử lý được response, sau đó nó mới trả về.

Hãy cùng xem 2 ví dụ bên dưới để bớt khó hiểu hơn 😄.

Ví dụ 1.

var xhr = new XMLHttpRequest(); //Tạo mới đối tượng
xhr.open("GET", url); //xác nhận URL
xhr.send(); //gửi request

Ở ví dụ này thì bất kỳ truy cập trực tiếp nào đến dữ liệu của response sau phương thức send() là vô ích, vì send không chờ cho đến khi request được hoàn thành. Nên nhớ, như đã đề cập ở trên, XMLHttpRequest mặc định được thiết kế làm việc không đồng bộ.

Ví dụ 2.

var xhr = new XMLHttpRequest();
xhr.open("GET", "hello.txt");
xhr.send();
document.write(xhr.response);
// empty string

Với file hello.txt chỉ là chứa text đơn giản, thuộc tính response của XHR không hợp lệ cho nên ta không in được text ra.

XHR thực hiện kiểm tra response cứ sau mỗi mili giây. Khi response đã được load, sự kiện onload của XHR được dùng để cung cấp đầu ra hợp lệ.

var xhr = new XMLHttpRequest();
xhr.open("GET", "hello.txt");
xhr.send();
xhr.onload = function(){ document.write(this.response) }
// writes 'hello' to the document

Một cách đơn giản hơn để response được xử lý đồng bộ là ta sẽ truyền false vào làm đối số cuối cùng cho method open như sau :

var xhr = new XMLHttpRequest();
xhr.open("GET", "hello.txt", false);
xhr.send();
document.write(xhr.response);
// writes 'hello' to document

Truyền flag này với giá trị là false có nghĩa là đây là làm việc đồng bộ.

Tham khảo


Hi vọng bài viết này sẽ giúp bạn hiểu rõ hơn về đồng bộ và không đồng bộ trong JavaScript. Cám ơn bạn đã theo dõi bài viết