Có một cú pháp đặc biệt để làm việc với những lời hứa theo cách thoải mái hơn, được gọi là async / await. Thật đáng ngạc nhiên dễ hiểu và sử dụng.

1. Hàm không đồng bộ

Hãy bắt đầu với asynctừ khóa. Nó có thể được đặt trước một hàm, như thế này:

async function f() {
  return 1;
}

Từ “async” trước một hàm có nghĩa là một điều đơn giản: một hàm luôn trả về một lời hứa. Các giá trị khác được gói trong một lời hứa được giải quyết tự động.

Chẳng hạn, hàm này trả về một lời hứa đã được giải quyết với kết quả là 1; Hãy kiểm tra nó:

async function f() {
  return 1;
}

f().then(alert); // 1

Chúng ta hoàn toàn có thể trả lại một lời hứa, điều này sẽ giống nhau:

async function f() {
  return Promise.resolve(1);
}

f().then(alert); // 1

Vì vậy, asyncđảm bảo rằng hàm trả về một lời hứa và kết thúc các lời hứa trong đó. Đủ đơn giản, phải không? Nhưng không chỉ có thế. Có một từ khóa khác, awaitchỉ hoạt động bên trong các asynchàm , và nó khá tuyệt.

2. Await

Cú pháp:

// works only inside async functions
let value = await promise;

Từ khóa awaitlàm cho JavaScript chờ cho đến khi lời hứa đó làm xong và trả về kết quả của nó.

Đây là một ví dụ với lời hứa sẽ giải quyết trong 1 giây:



async function f() {

  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("done!"), 1000)
  });

  let result = await promise; // wait until the promise resolves (*)

  alert(result); // "done!"
}

f();

Việc thực thi hàm đã tạm dừng các dòng tại dòng (*)và tiếp tục lại khi lời hứa đã ổn định, và resulttrở thành kết quả của nó. Vì vậy, đoạn code trên cho thấy đã thực hiện! trong một giây

Chúng ta hãy nhấn mạnh: awaitnghĩa đen là làm cho JavaScript chờ cho đến khi lời hứa được giải quyết, rồi tiếp tục với kết quả. Điều đó không tốn bất kỳ tài nguyên CPU nào, bởi vì công cụ có thể thực hiện các công việc khác trong thời gian này: thực thi các tập lệnh khác, xử lý các sự kiện, v.v.

Đó chỉ là một cú pháp tao nhã để nhận được kết quả hứa hẹn hơn promise.then, dễ đọc và viết hơn.

Không thể sử dụng awaittrong các chức năng thông thường

Nếu chúng ta cố gắng sử dụng hàmawait không đồng bộ, sẽ có lỗi cú pháp:



function f() {
  let promise = Promise.resolve(1);
  let result = await promise; // Syntax error
}

Chúng ta sẽ nhận được lỗi này nếu chúng ta không đặt asynctrước một hàm. Như đã nói, awaitchỉ hoạt động bên trong một async function.

Hãy lấy showAvatar()ví dụ từ chương Một chuỗi Hứa hẹn và viết lại bằng cách sử dụng async/await:

  1. Chúng ta sẽ cần phải thay thế các cuộc gọi.then bằng await.
  2. Ngoài ra chúng ta nên làm cho hàm làasync để nó làm việc.
async function showAvatar() {

  // read our JSON
  let response = await fetch('/article/promise-chaining/user.json');
  let user = await response.json();

  // read github user
  let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
  let githubUser = await githubResponse.json();

  // show the avatar
  let img = document.createElement('img');
  img.src = githubUser.avatar_url;
  img.className = "promise-avatar-example";
  document.body.append(img);

  // wait 3 seconds
  await new Promise((resolve, reject) => setTimeout(resolve, 3000));

  img.remove();

  return githubUser;
}

showAvatar();

Khá sạch sẽ và dễ đọc, phải không? Tốt hơn nhiều so với trước đây.await sẽ không làm việc trực tiếp với code bất đồng bộ

Những người mới bắt đầu sử dụng awaitcó xu hướng quên mất thực tế là chúng ta không thể sử dụng codeawaitvới code bất đồ bộ mà cần phải tham qua một hàm . Ví dụ: điều này sẽ không hoạt động:

// syntax error in top-level code
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();

Nhưng chúng ta có thể gói nó thành một hàm async ẩn danh, như thế này:

(async () => {
  let response = await fetch('/article/promise-chaining/user.json');
  let user = await response.json();
  ...
})();

chấp nhận await

Giống như promise.then, awaitcho phép chúng ta sử dụng các đối tượng có thể đọc được (những đối tượng có phương thứcthen có thể gọi được ). Ý tưởng là một đối tượng bên thứ ba có thể không phải là một lời hứa, nhưng tương thích với lời hứa: nếu nó hỗ trợ .then, điều đó đủ để sử dụng nó với await.

Đây là một lớpThenable demo ; các awaitbên dưới chấp nhận trường hợp của nó:

class Thenable {
  constructor(num) {
    this.num = num;
  }
  then(resolve, reject) {
    alert(resolve);
    // resolve with this.num*2 after 1000ms
    setTimeout(() => resolve(this.num * 2), 1000); // (*)
  }
};

async function f() {
  // waits for 1 second, then result becomes 2
  let result = await new Thenable(1);
  alert(result);
}

f();

Nếu awaitnhận được một đối tượng không hứa hẹn .then, nó gọi phương thức đó cung cấp các hàm dựng sẵn resolverejectlàm đối số (giống như đối với Promisethông thường ). Sau đó awaitđợi cho đến khi một trong số chúng được gọi (trong ví dụ trên nó xảy ra trong dòng (*)) và sau đó tiến hành kết quả.

Phương thức lớp không đồng bộ

Để khai báo một phương thức lớp async, chỉ cần thêm nó vào async:

class Waiter {
  async wait() {
    return await Promise.resolve(1);
  }
}

new Waiter()
  .wait()
  .then(alert); // 1

Ý nghĩa là như nhau: nó đảm bảo rằng giá trị trả về là một lời hứa và cho phép await.

3. Xử lý lỗi

Nếu một lời hứa giải quyết bình thường, sau đó await promisetrả về kết quả. Nhưng trong trường hợp từ chối, nó sẽ ném lỗi, giống như có một câu lệnh throw tại dòng đó.

Code này:


async function f() {
  await Promise.reject(new Error("Whoops!"));
}

… cũng giống như thế này:

async function f() {
  throw new Error("Whoops!");
}

Trong tình huống thực tế, lời hứa có thể mất một thời gian trước khi nó từ chối. Trong trường hợp đó sẽ có độ trễ trước khi awaitđưa ra lỗi.

Chúng ta có thể bắt lỗi đó bằng cách sử dụng try..catch, giống như cách thông thường throw:


async function f() {

  try {
    let response = await fetch('http://no-such-url');
  } catch(err) {
    alert(err); // TypeError: failed to fetch
  }
}

f();

Trong trường hợp có lỗi, điều khiển nhảy vào khốicatch. Chúng ta cũng có thể gói nhiều dòng:

async function f() {

  try {
    let response = await fetch('/no-user-here');
    let user = await response.json();
  } catch(err) {
    // catches errors both in fetch and response.json
    alert(err);
  }
}

f();

Nếu chúng ta không có try..catch, thì lời hứa được tạo bởi lệnh gọi của hàm async f()sẽ bị từ chối. Chúng tôi có thể chắp thêm .catchđể xử lý nó:

async function f() {
  let response = await fetch('http://no-such-url');
}

// f() becomes a rejected promise
f().catch(alert); // TypeError: failed to fetch // (*)

Nếu chúng ta quên thêm vào .catchđó, thì chúng ta sẽ gặp lỗi lời hứa chưa được xử lý (có thể xem trong console). Chúng ta có thể bắt lỗi như vậy bằng cách sử dụng trình xử lýunhandledrejection sự kiện toàn cầu như được mô tả trong chương Xử lý lỗi với các lời hứa.async/awaitpromise.then/catch

Khi chúng ta sử dụng async/await, chúng ta hiếm khi cần .then, bởi vì awaitxử lý sự chờ đợi cho chúng ta. Và chúng ta có thể sử dụng thường xuyên try..catchthay vì .catch. Điều đó thường (nhưng không phải luôn luôn) thuận tiện hơn.

Nhưng ở cấp cao nhất của code, khi chúng ta ở ngoài bất kỳ hàmasync nào, chúng ta không thể sử dụng về mặt cú pháp await, do đó, đây là một cách thông thường để thêm .then/catchvào để xử lý kết quả cuối cùng hoặc lỗi thông qua, như trong (*)ví dụ ở trên.async/await hoạt động tốt với Promise.all

Khi chúng ta cần chờ đợi nhiều lời hứa, chúng ta có thể gói chúng lại Promise.allvà sau đó await:

// wait for the array of results
let results = await Promise.all([
  fetch(url1),
  fetch(url2),
  ...
]);

Trong trường hợp có lỗi, nó sẽ lan truyền như bình thường, từ lời hứa thất bại đến Promise.all, và sau đó trở thành một ngoại lệ mà chúng ta có thể bắt gặp khi sử dụng try..catchxung quanh cuộc gọi.

4. Tóm lược

Các từ khóaasync trước khi một hàm có hai tác dụng:

  1. Làm cho nó luôn luôn trả lại một lời hứa.
  2. Cho phép awaitđược sử dụng trong nó.

Các từ khóaawait trước một lời hứa làm cho chờ đợi cho đến khi hoạt Javascript rằng Settles lời hứa, và sau đó:

  1. Nếu đó là một lỗi, ngoại lệ được tạo ra – giống như khi throw errorđược gọi tại chính vị trí đó.
  2. Nếu không, nó trả về kết quả.

Họ cùng nhau cung cấp một khung tuyệt vời để viết code không đồng bộ, dễ đọc và viết.

Với async/awaitchúng ta hiếm khi cần phải viết promise.then/catch, nhưng chúng ta vẫn không nên quên rằng chúng dựa trên những lời hứa, bởi vì đôi khi (ví dụ trong phạm vi ngoài cùng), chúng ta phải sử dụng các phương pháp này. Cũng Promise.alltốt khi chúng ta đang chờ đợi nhiều nhiệm vụ cùng một lúc.

Full series tự học Javascript từ cơ bản tới nâng cao tại đây nha.

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!