1 September 2021

Async/await

Ada sintaksis spesial untuk bekerja dengan promise dengan cara yang lebih nyaman, dipanggil ???async/await???. Ini sangat mudah dipahami dan digunakan.

Fungsi Async

Mari mulai dengan keyword async. keyword ini dapat ditempatkan sebelum fungsi, seperti ini:

async function f() {
  return 1;
}

Kata ???async??? sebelum fungsi berarti satu hal sederhana: fungsi tersebut selalu mengembalikkan promise. Value lain dibungkus didalam promise yang resolve secara otomatis.

Sebagai contoh, fungsi ini mengembalikkan promise yang resolve dengan hasil 1, mari kita uji:

async function f() {
  return 1;
}

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

???Kita secara eksplisit dapat mengembalikkan promise, itu akan sama dengan:

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

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

Jadi, async memastikan bahwa fungsi mengembalikkan promise, dan membungkus non-promise di dalamnya. Cukup mudah, bukan? Tapi tidak hanya itu. Ada keyword lain, await, yang hanya bekerja di dalam fungsi async, dan itu cukup keren.

Await

Sintaksis:

// bekerja hanya di dalam fungsi async
let value = await promise;

Keyword await membuat JavaScript menunggu sampai promise tersebut selesai dan mengembalikkan hasilnya.

Ini contoh dengan promise yang selesai dalam 1 detik:

async function f() {

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

  let result = await promise; // tunggu sampai promise selesai (*)

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

f();

Eksekusi fungsi tersebut ???dipause??? pada baris (*) dan dilanjutkan ketika promise selesai, dengan result menjadi hasilnya. Jadi kode di atas menunjukkan ???done!??? dalam satu detik.

Mari kita tekankan: await benar-benar membuat JavaScript menunggu sampai promise selesai, lalu lanjutkan dengan hasilnya. Hal tersebut tidak membebani resource CPU apapun, karena mesin dapat melakukan pekerjaan lain sementara itu: eksekusi script lain, menangani event dan lain-lain.

Ini hanya sintaksis yang lebih elegan untuk mendapatkan hasil dari promise daripada promise.then, mudah untuk dibaca dan ditulis.

Tidak dapat menggunakan awaitdi dalam fungsi biasa
function f() {
  let promise = Promise.resolve(1);
  let result = await promise; // Error sintaksis
}

Kita akan mendapatkan error ini jika kita tidak meletakkan async sebelum fungsi. Seperti yang dikatakan, await hanya bekerja di dalam sebuah fungsi async.

Mari kita ambil contoh showAvatar() dari bab Promises chaining dan menulisnya ulang menggunakan async/await:

  1. Kita harus mengganti call .then dengan await.
  2. Juga kita harus membuat fungsi async agar mereka bekerja.
async function showAvatar() {

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

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

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

  // tunnggu 3 detik
  await new Promise((resolve, reject) => setTimeout(resolve, 3000));

  img.remove();

  return githubUser;
}

showAvatar();

Cukup bersih dan mudah dibaca, bukan? Jauh lebih baik dari sebelumnya.

await tidak bekerja pada top-level code

Orang yang baru mulai menggunakan await cenderung lupa faktanya bahwa kita tidak bisa menggunakan await pada top-level code. Sebagai contoh, ini tidak akan bekerja:

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

Kita dapat membungkusnya kedalam fungsi async anonymous, seperti ini:

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

P.S. Fitur baru: mulai dari mesin V8 versi 8.9+, tingkat atas menunggu bekerja di [modul](info: modul).

````smart header="`await`menerima \"thenables\"" Seperti`promise.then`, `await`memperbolehkan kita menggunakan objek thenable (mereka dengan method`then`callable). Idenya adalah objek 3rd-party mungkin bukan promise, tetapi promise-compatible: jika objek tersebut mendukung`.then`, itu cukup digunakan dengan `await`.

Disini demo class `Thenable`, `await` di bawah menerima instances dari class:

```js run
class Thenable {
  constructor(num) {
    this.num = num;
  }
  then(resolve, reject) {
    alert(resolve);
    // resolve dengan this.num*2 setelah 1000ms
    setTimeout(() => resolve(this.num * 2), 1000); // (*)
  }
}

async function f() {
  // tunggu selama 1 detik, kemudian result-nya menjadi 2
  let result = await new Thenable(1);
  alert(result);
}

f();
```

Jika `await` mendapatkan objek non-promise dengan `.then`, objek itu memanggil method yang menyediakan fungsi asli `resolve`, `reject` sebagai argumen. Kemudian `await` menunggu sampai salah satu argumen tersebut dipanggil (pada contoh di atas hal tersebut terjadi pada baris `(*)`) dan kemudian melanjutkan dengan hasilnya.
Method class async

Untuk mendeklarasikan sebuah method class async, cukup tambahkan saja async:

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

new Waiter()
  .wait()
  .then(alert); // 1 (this is the same as (result => alert(result)))

Artinya sama saja: itu memastikan bahwa value yang dikembalikkan adalah promise dan memungkinkan await.

Penanganan error

Jika sebuah promise resolve secara normal, kemudian await promise mengembalikkan result. Tetapi dalam kasus rejection, await promise melempar error, seolah olah ada pernyataan throw pada baris tersebut.

Kode ini:

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

???Sama dengan ini:

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

Di dalam situasi yang nyata, promise mungkin butuh waktu sebelum reject. Dalam hal ini akan terjadi penundaan sebelum await melemparkan sebuah error.

Kita dapat catch error itu menggunakan try..catch, dengan cara yang sama seperti throw biasa:

async function f() {

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

f();

Dalam kasus error, kontrolnya meloncat ke blok catch. kita juga dapat membungkus banyak baris:

async function f() {
  try {
    let response = await fetch("/no-user-here");
    let user = await response.json();
  } catch (err) {
    // catch error baik di fetch dan response.json
    alert(err);
  }
}

f();

Jika kita tidak punya try..catch, maka promise yang dihasilkan oleh pemanggilan fungsi async f() menjadi direject. Kita dapat menambahkan .catch untuk menanganinya:

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

// f() menjadi sebuah promise yang direject
f().catch(alert); // TypeError: failed to fetch (gagal mengambil resource) // (*)

Jika kita lupa menambahkan .catch di sana, maka kita mendapatkan sebuah error promise yang tak tertangani (dapat dilihat di console). Kita dapat catch errors seperti itu menggunakan handler event global seperti yang dijelaskan di bab Penanganan error dengan promise.

async/awaitdanpromise.then/catch

Tetapi pada top-level code, saat kita berada di luar fungsi async, kita secara sintaks tidak dapat menggunakan await, jadi itu sebuah latihan normal untuk menambah .then/catch untuk menangani hasil akhir atau jika terjadi error.

Seperti baris (*) contoh di atas.

async/await bekerja baik dengan Promise.all

Ketika kita perlu menunggu banyak promise, kita dapat membungkusnya di dalam Promise.all dan kemudian await:

// tunggu untuk result dalam array
let results = await Promise.all([
  fetch(url1),
  fetch(url2),
  ...
]);

Pada kasus error, itu menyebar seperti biasa: dari promise yang gagal ke Promise.all, dan kemudian menjadi sebuah pengecualian yang bisa kita catch menggunakan try..catch di sekitar pemanggilan.

## Ringkasan

Keyword `async` sebelum fungsi memiliki dua efek:

1. Membuatnya selalu mengembalikkan sebuah promise.
2. Memperbolehkan kita untuk menggunakan `await` di dalamnya.

Keyword `await` sebelum promise membuat JavaScript menunggu sampai promise itu selesai, dan kemudian:

1. Jika ada error, pengecualian dihasilkan, sama seperti jika `throw error` dipanggil di tempat itu.
2. Sebaliknya, menghasilkan result.

Bersama mereka menyediakan kerangka kerja yang bagus untuk menulis kode asynchronous yang mudah baik membaca dan menulis.

Dengan `async/await` kita jarang menulis `promise.then/catch`, tetapi kita tetap tidak boleh lupa bahwa `async/await` berdasarkan promise, karena terkadang (misalnya di scope terluar) kita harus menggunakan method ini. Juga `Promise.all` adalah sesuatu yang bagus untuk menunggu banyak task secara bersamaan.

Tugas

Tulis ulang salah satu contoh di bab ini Promises chaining menggunakan async/await daripada .then/catch:

function loadJson(url) {
  return fetch(url)
    .then(response => {
      if (response.status == 200) {
        return response.json();
      } else {
        throw new Error(response.status);
      }
    });
}

loadJson('no-such-user.json')
  .catch(alert); // Error: 404

Catatannya ada di bawah kode:

async function loadJson(url) {
  // (1)
  let response = await fetch(url); // (2)

  if (response.status == 200) {
    let json = await response.json(); // (3)
    return json;
  }

  throw new Error(response.status);
}

loadJson("no-such-user.json").catch(alert); // Error: 404 (4)

Catatan:

  1. Function loadJson menjadi async.

  2. Semua .then di dalamnya di ganti dengan await.

  3. Kita dapat return response.json() daripada menunggu untuk itu, seperti ini:

    if (response.status == 200) {
      return response.json(); // (3)
    }

    Lalu kode terluar harus await untuk promise tersebut resolve. Dalam kasus kita, itu tidak masalah.

  4. Error yang dilempar dari loadJson ditangani oleh .catch. Kita tidak bisa menggunakan await loadJson(???) di sana, karena kita tidak berada di dalam function async.

Di bawah anda dapat menemukan contoh ???rethrow??? dari bab Promises chaining. Tulis ulang menggunakan async/await daripada .then/catch.

Dan singkirkan rekursi yang mendukung masuk loop dalam demoGithubUser: dengan async/await itu menjadi mudah untuk dilakukan.

class HttpError extends Error {
  constructor(response) {
    super(`${response.status} for ${response.url}`);
    this.name = "HttpError";
    this.response = response;
  }
}

function loadJson(url) {
  return fetch(url).then((response) => {
    if (response.status == 200) {
      return response.json();
    } else {
      throw new HttpError(response);
    }
  });
}

// Tanya nama pengguna sampai github mengembalikkan user yang valid
function demoGithubUser() {
  let name = prompt("Enter a name?", "iliakan");

  return loadJson(`https://api.github.com/users/${name}`)
    .then((user) => {
      alert(`Full name: ${user.name}.`);
      return user;
    })
    .catch((err) => {
      if (err instanceof HttpError && err.response.status == 404) {
        alert("No such user, please reenter.");
        return demoGithubUser();
      } else {
        throw err;
      }
    });
}

demoGithubUser();

Tidak ada trik di sini. Hanya mengganti .catch dengan try...catch di dalam demoGithubUser dan menambahkan async/await ketika dibutuhkan:

class HttpError extends Error {
  constructor(response) {
    super(`${response.status} for ${response.url}`);
    this.name = "HttpError";
    this.response = response;
  }
}

async function loadJson(url) {
  let response = await fetch(url);
  if (response.status == 200) {
    return response.json();
  } else {
    throw new HttpError(response);
  }
}

// Tanya nama pengguna sampai github mengembalikkan pengguna yang valid
async function demoGithubUser() {
  let user;
  while (true) {
    let name = prompt("Enter a name?", "iliakan");

    try {
      user = await loadJson(`https://api.github.com/users/${name}`);
      break; // tidak ada error, keluar dari loop
    } catch (err) {
      if (err instanceof HttpError && err.response.status == 404) {
        // loop dilanjutkan setelah alert
        alert("No such user, please reenter.");
      } else {
        // error yang tidak diketahui, rethrow
        throw err;
      }
    }
  }

  alert(`Full name: ${user.name}.`);
  return user;
}

demoGithubUser();

Kita punya function ???reguler???. Bagaimana memanggil asyncdari function tersebut dan menggunakan hasilnya?

async function wait() {
  await new Promise((resolve) => setTimeout(resolve, 1000));

  return 10;
}

function f() {
  // ...apa  yang ditulis di sini?
  // kita harus memanggil async wait() dan tunggu sampai mendapatkan 10
  // ingat, kita tidak bisa menggunakan "await"
}

P.S. Task ini secara teknis sangat mudah, tetapi pertanyaan ini cukup umum bagi developer yang baru mengenal async/await.

Itulah yang terjadi ketika mengetahui cara kerjanya di dalam sangat membantu.

Hanya perlakukan pemanggilan async sebagai promise dan lampirkan .then ke dalamnya:

async function wait() {
  await new Promise(resolve => setTimeout(resolve, 1000));

  return 10;
}

function f() {
  // menunjukkan 10 setelah 1 detik
  wait().then(result => alert(result));
}

f();
Peta tutorial