Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> JavaScript

JavaScript không có phương thức Sleep: Giải pháp thay thế

Nếu bạn là kỹ sư hoặc nhà phát triển đến từ ngôn ngữ lập trình khác, có thể đã dùng phương thức native sleep() để trì hoãn hoặc tạm dừng thực thi một phương thức. JavaScript không có phương thức native như vậy.

Trong bài viết này, ta sẽ nói về hai cách dùng JavaScript mô phỏng hàm sleep: Promise và async/await.

Giới thiệu về Hàm Bất đồng bộ (Asynchronous Functions)

Không nên nói về Promise hay async/await trong JavaScript mà không nhắc trước phương thức setTimeout(). Phương thức này cho thấy tại sao Promise cần thiết trong JavaScript.

JavaScript là ngôn ngữ đơn luồng (Single-Threaded)

Khi nói JavaScript là ngôn ngữ đơn luồng, nghĩa là JavaScript chỉ có một call stack (ngăn xếp gọi) và một memory heap (đống bộ nhớ). Ở mức cao, nghĩa là JavaScript đọc code, từng dòng một, theo thứ tự, và phải thực thi một đoạn code trước khi chuyển dòng tiếp theo. Điều này làm JavaScript đồng bộ (synchronous) bởi bản chất. Tuy nhiên, đôi khi cần giải pháp thay thế để làm code bất đồng bộ (asynchronous).

Code Đồng bộ vs Bất đồng bộ

Xem ví dụ đồng bộ:

// synchronous
console.log("In đầu tiên")
console.log("In thứ hai")
console.log("In thứ ba");

Rất đơn giản: mỗi console.log in theo trình tự vì JavaScript thực thi theo trình tự.

Giả sử muốn dòng 2 in trước dòng 3? Làm sao in console.log đó "ngoài luồng"? Dùng setTimeout():

// setTimeout
console.log("In đầu tiên")
setTimeout(() => {
  console.log("In thứ ba")
}, 1000);
console.log("In thứ hai");

setTimeout() cho phép thực thi hàm JavaScript mà không chặn luồng (non-blocking) để code khác chạy. Tham số đầu là hàm callback chạy sau thời gian định (tham số thứ hai). Tham số thứ hai tính bằng milliseconds.

setTimeout() mô phỏng phương thức sleep native trong ngôn ngữ khác bằng:

  1. Đặt bộ đếm thời gian nền khi engine JavaScript thực thi setTimeout()
  2. Tiếp tục chạy code khác trong lúc bộ đếm đếm ngược
  3. Thực thi hàm callback trong setTimeout() khi bộ đếm về 0.

Hiểu cách setTimeout() hoạt động là chìa khóa để hiểu Promise và async/await. Ta sẽ nói về Promise tiếp theo.

Promise

Tạo Promise

Promise là cách thực hiện logic bất đồng bộ. Constructor Promise nhận hàm callback có hai tham số: resolve và reject. Hàm callback chứa logic, khi kết thúc sẽ gọi resolve hoặc reject với phản hồi truyền vào.

console.log("before promise")
let promise = new Promise((resolve, reject) => {
  let resolvedFlag = false; // cờ để test logic
  console.log("first");
  console.log("second")
  console.log("third")
  console.log("fourth");
  resolvedFlag = true; // đổi resolved thành true sau khi xong console logs
  if(resolvedFlag) {
    // nếu resolved true thì gọi resolve
    resolve("Promise resolved");
    console.log("after promise");
  } else {
    // ngược lại gọi reject với Error object
    reject(new Error("Promise failed"));
    console.log("after promise");
  }
});

Đoạn code này demo một Promise đơn giản. Promise có ba trạng thái:

  • pending – Chưa resolved cũng chưa rejected – trạng thái ban đầu
  • resolved – Thực thi thành công
  • rejected – Lỗi khi thực thi

Thử đổi resolvedFlag từ true sang false trong code trên để demo resolved và rejected promise.

Điều quan trọng: Promise chứa hàm tạm dừng thực thi script cho đến khi Promise resolved hoặc rejected. Sau đó script tiếp tục.

Sử dụng phản hồi Promise

Khi khởi tạo instance của Promise, dùng then() và catch() để bố trí logic muốn dùng sau khi nhận phản hồi từ Promise trả về. Cấu trúc cấp cao:

promise.then(func).catch(func);

Trong ngoặc đơn gọi then và catch là hàm nặc danh có phản hồi truyền làm tham số.

promise // promise tạo ở trên
  .then(response => {
    // chạy nếu thành công
    console.log(response);
    console.log("after promise");
  })
  .catch(error => {
    // bắt lỗi ở đây
    console.log(error.message);
    console.log("after promise");
  })

Promise thường dùng khi gọi database hoặc request HTTP.

Async/Await Functions

Cách cuối mô phỏng sleep() là dùng async/await. Về bản chất, các hàm bất đồng bộ này chỉ là cách xây dựng logic giống Promise nhưng code ít hơn.

Từ khóa async đặt trước function trong pre-ES6, và trước tham số trong ES6+. Logic muốn làm trước khi chuyển tiếp đặt trong block này.

const firstFunction = () => {
  // logic muốn làm bất đồng bộ
  let resolved = true;
  if(resolved) {
    let respObj = { message: "Resolved!"};
    return respObj.message;
  } else {
    let errObj = { message: "Error!"};
    return errObj.message;
  }
}

const asyncExample = async () => {
  // async keyword trước tham số
  console.log("Calling function...");
  let result = await firstFunction(); // await trước lời gọi hàm chứa logic
  // không đi dòng tiếp theo cho đến khi hàm xong
  console.log(result);
  return result;
}

asyncExample() // gọi hàm async/await

Dùng async/await cho quy trình bất đồng bộ dựa trên Promise gần như giống hệt nhưng code ít hơn.

Kết luận

Bài viết đã xem các cách mô phỏng hàm sleep() native trong ngôn ngữ khác. Dùng setTimeout() demo khái niệm hàm bất đồng bộ JavaScript để có thể xem Promise và async/await.