JavaScript và các cơ chế xử lý hoạt động đồng bộ

Việc cải tiến khả năng xử lý các hoạt động bất đồng bộ thành một chuỗi hoạt động đồng bộ được diễn ra liên tục trong suốt chiều dài phát triển của ngôn ngữ JavaScript. Trong bài viết này, chúng ta sẽ đến với 4 khái niệm quan trọng để làm chủ được các vấn đề về xử lý đồng bộ bằng JavaScript.

Callback

Lệnh Callback trong JavaScript là một hàm được truyền cho một hàm khác dưới dạng tham số và được thực thi hoặc gọi trong hàm đó. Hàm Callback cần phải đợi một hàm khác thực thi hoặc trả về một giá trị, từ đó tạo ra một chuỗi các hàm được hình thành – phương pháp này được sử dụng để tạo một hoạt động đồng bộ trong JavaScript. Vì JavaScript vốn hoạt động bất đồng bộ.

const sayHello = (greeting) => {
console.log(greeting + '' + 'Earthling');
}

const greet = (callback) => {
greeting = "Bonjour";
callback(greeting);
}greet(sayHello);//Bonjour Earthling

Trong ví dụ trên, hàm sayHello được truyền làm đối số callback cho hàm greet. Trước khi sayHello được thực thi, nó sẽ đợi greet thực thi.

Promise

Theo developer.mozilla:

“Promise là một đối tượng đại diện cho trạng thái cuối cùng của một hoạt động bất đồng bộ là hoàn thành hoặc thất bại. Về cơ bản, một promise là một đối tượng được trả về mà chúng ta đính kèm các lệnh callback, thay vì truyền các lệnh callback vào một hàm. ”

Đối tượng Promise đại diện cho sự thành công của một hoạt động bất đồng bộ cũng như giá trị kết quả của nó.

Các Promise được sử dụng để tránh bị kẹt trong các lệnh Callback lồng trong các lệnh Callback khác. Có một Callback trong một Callback giống như đệ quy vậy, nếu không có sự hỗ trợ của Promise, chúng ta có thể dễ dàng bị mắc kẹt trong một chu kỳ liên tục lặp lại. Các Promise hữu ích nhất khi các hoạt động bất đồng bộ của bạn khi chuỗi hoạt động đồng bộ mà bạn muốn xây dựng chứa hai hoặc nhiều hoạt động. 

Promise có ba trạng thái khác nhau – đã hoàn thành (fulfilled), bị từ chối (rejected) và đang chờ xử lý (pending). Đã hoàn thành được trả lại khi một hoạt động hoàn tất thành công. Bị từ chối là khi hoạt động không thành công. Đang chờ xử lý là khi trạng thái ban đầu được trả lại, không phải là hoàn thành hoặc bị từ chối. Cấu trúc của Promise như sau:

const promise = new Promise((resolve, reject) => {
isMine = true;
if(isMine) {
resolve("This cat is mine")
} else {
reject("This cat is not mine")
}

promise.then(result => console.log(result))
.catch(() => {
console.log('error!')
)}

//This cat is mine
//Promise {<resolved>: undefined}

Async & Await 

Tương tự như Promise, AsyncAwait cung cấp một phương thức để duy trì các hoạt động bất đồng bộ một cách đồng bộ hơn.

Async & Await có thể được sử dụng trong một API REST request khi muốn dữ liệu tải đầy đủ trước khi hiển thị cho người dùng.

const displayPeople = async () => {
const response = await fetch('https://example.com/people');
const people = await response.json();

console.log(people);
}

displayPeople();

//

Để JavaScript biết rằng chúng ta đang làm việc với Promises, “await” cần được bao bọc trong một hàm “async”. Ở ví dụ trên, chúng ta đang chờ đợi hai thứ – response  và people. Trước khi chuyển đổi response  sang JSON, phải xác nhận rằng response  đã được tìm nạp – nếu response chưa xuất hiện, việc chuyển đổi sang JSON sẽ dẫn đến lỗi (do đó, cần await).

LIFO trong Call Stack

Để quản lý ngữ cảnh thực thi (Global Execution Context và Function Execution Context), JavaScript sử dụng Call stack.Call stack này sử dụng khái niệm LIFO – Last In First Out. Khi thực thi một tập lệnh, JavaScript tạo một Global Execution Context và đẩy nó lên đầu Call stack.

Mỗi khi một hàm được gọi, JavaScript sẽ tạo một Ngữ Function Execution Context cho hàm, đẩy hàm này lên đầu Call stack và bắt đầu thực thi hàm.

Khi một hàm gọi một hàm khác, JavaScript sẽ tạo một Function Execution Context mới cho hàm này và đẩy nó lên đầu Call stack. Khi hàm hiện tại được giải quyết, nó sẽ được đẩy ra khỏi Call stack và quá trình thực thi được tiếp tục tới khi nó dừng lại. Tập lệnh sẽ kết thúc khi Call stack trống.

Tác giả Denali Balser

Dịch bởi Devera Academy