Future dan async/await

Menangani operasi asinkron di Dart menggunakan Future, async, dan await beserta penanganan errornya.

Daftar isi

Operasi yang membutuhkan waktu, seperti membaca berkas atau mengambil data dari jaringan, tidak boleh membekukan antarmuka aplikasi. Dart menyelesaikannya dengan Future, yaitu objek yang mewakili nilai yang belum tersedia saat ini dan baru siap kemudian. Topik ini membahas cara membuat fungsi asinkron dengan async, menunggu hasilnya dengan await, menangani error asinkron dengan try-catch, serta membaca urutan eksekusi yang terlihat pada keluaran program.

Program pertama: mengambil catatan secara asinkron#

Program berikut menyimulasikan pengambilan data catatan dengan jeda singkat, termasuk satu permintaan yang gagal. Jeda menggunakan Future.delayed selama 100 hingga 200 milidetik, cukup untuk memperlihatkan urutan tanpa membuat program lambat. Simpan program berikut sebagai berkas catatan.dart, lalu jalankan perintah dart catatan.dart dari direktori tempat berkas itu berada.

dart catatan.dart
// Simulasi pengambilan data catatan secara asinkron dengan Future.

// Fungsi async: mengembalikan Future<String> yang selesai setelah jeda.
Future<String> ambilCatatan(String judul) async {
  await Future.delayed(const Duration(milliseconds: 200));
  return 'Isi catatan "$judul"';
}

// Fungsi async yang gagal: Future-nya selesai dengan error.
Future<String> ambilCatatanGagal(String judul) async {
  await Future.delayed(const Duration(milliseconds: 100));
  throw Exception('Catatan "$judul" tidak ditemukan');
}

Future<void> main() async {
  print('mulai');

  // Future dibuat dan langsung dijalankan; kode berikutnya tetap berjalan.
  final permintaan = ambilCatatan('Belajar Dart');
  print('permintaan dikirim');

  // await menunggu Future selesai, lalu hasilnya dipakai.
  final isi = await permintaan;
  print(isi);

  // Penanganan error pada kode asinkron dengan try-catch.
  try {
    final arsip = await ambilCatatanGagal('Arsip 2025');
    print(arsip);
  } catch (e) {
    print('gagal: $e');
  }

  // Future.wait: menunggu beberapa Future selesai sekaligus.
  final hasil = await Future.wait([
    ambilCatatan('Rencana Belajar'),
    ambilCatatan('Catatan Biaya'),
  ]);
  print(hasil);

  print('selesai');
}
text Keluaran
mulai
permintaan dikirim
Isi catatan "Belajar Dart"
gagal: Exception: Catatan "Arsip 2025" tidak ditemukan
[Isi catatan "Rencana Belajar", Isi catatan "Catatan Biaya"]
selesai

Urutan baris keluaran memperlihatkan perilaku yang menjadi sasaran pembelajaran topik ini. Baris permintaan dikirim tercetak lebih dulu daripada isi catatan, karena membuat Future tidak sama dengan menunggunya. Baris gagal: Exception: ... membuktikan error asinkron tertangkap oleh try-catch, lalu program tetap melanjutkan eksekusi hingga baris selesai tercetak. Baris daftar hasil Future.wait menunjukkan dua permintaan yang berjalan bersamaan, dengan hasil dikembalikan terurut sesuai urutan argumennya.

Urutan eksekusi dan event loop#

Dart menjalankan kode dalam satu event loop, yakni mekanisme yang memproses pekerjaan satu per satu dari antrean. Kode sinkron di dalam main() berjalan sampai tuntas terlebih dahulu, sedangkan pekerjaan yang ditunda, seperti Future.delayed, masuk ke antrean untuk diproses kemudian. Pada program, pemanggilan ambilCatatan('Belajar Dart') langsung menjalankan bagian awal fungsi tersebut hingga menemui await, lalu kendali kembali ke main() sehingga baris permintaan dikirim tercetak sebelum fungsi ambilCatatan() selesai.

Pernyataan await menunda kelanjutan fungsi asinkron yang memuatnya, bukan membekukan seluruh aplikasi. Selama menunggu, event loop tetap memproses pekerjaan lain, dan itulah alasan antarmuka Flutter tetap responsif ketika data sedang dimuat. Apabila await dihilangkan, program tetap berjalan tetapi hasilnya belum siap, sehingga kode yang bergantung pada hasilnya harus selalu diletakkan setelah await. Membaca ulang urutan keluaran program di atas dengan cermat membantu membiasakan model eksekusi ini.

async dan await#

Fungsi yang memuat await harus ditandai async, dan nilai kembaliannya otomatis dibungkus menjadi Future. Deklarasi Future<String> ambilCatatan(String judul) async mengembalikan String biasa di dalam tubuhnya, tetapi bagi pemanggil nilai itu tiba sebagai Future<String> yang selesai setelah jeda. Fungsi main() juga dapat ditandai async dengan tipe Future<void>, sebagaimana pada program.

Kata kunci await digunakan pada ekspresi bertipe Future untuk mengambil hasilnya setelah selesai. Untuk beberapa Future sekaligus, Future.wait menerima daftar Future yang sudah berjalan, menunggu semuanya selesai, lalu menghasilkan daftar nilai terurut sesuai argumennya. Pada program, dua ambilCatatan di dalam Future.wait menunggu bersamaan, sehingga total waktunya mendekati durasi permintaan terlama, bukan jumlah keduanya. Pola ini berguna ketika beberapa permintaan tidak saling bergantung satu sama lain.

await juga mengubah tipe ekspresinya. Apabila ambilCatatan bertipe Future<String>, maka await ambilCatatan(...) bertipe String biasa, sehingga variabel isi pada program dapat dideklarasikan final tanpa anotasi Future. Aturan yang sama membuat penerusan hasil ke fungsi lain menjadi lugas: hasil await diperlakukan seperti nilai sinkron. Pola ini membuat kode asinkron Dart terbaca hampir seperti kode sinkron, dan itulah alasan async-await lebih dipilih daripada menyusun rantai callback.

Penanganan error dengan try-catch#

Error pada kode asinkron tidak dilempar langsung seperti pada kode sinkron, karena error tersebut tiba belakangan bersama Future yang selesai. Tempat yang tepat untuk menangkapnya adalah pemanggilan await di dalam blok try. Pada program, ambilCatatanGagal melempar Exception setelah jeda, dan catch (e) menangkapnya sehingga program mencetak baris gagal: ... lalu melanjutkan eksekusi sampai tuntas.

Pernyataan throw di dalam fungsi async tidak melempar error seketika, melainkan menyelesaikan Future-nya dengan error tersebut. Apabila error itu tidak pernah ditangkap, eksekusi program konsol berhenti dengan error tak tertangani, sedangkan di Flutter errornya dilaporkan ke lapisan penanganan error framework. Filter tipe dapat ditambahkan dengan on, misalnya on Exception catch (e) yang hanya menangkap Exception. Blok finally tetap dijalankan setelah try atau catch selesai, sebagaimana perilaku pada kode sinkron, sehingga pekerjaan pembersihan tetap terjamin.

Kesalahan umum#

  1. Melupakan await pada pemanggilan fungsi asinkron. Pemanggilan tanpa await tetap menjalankan pekerjaannya, tetapi kode berikutnya dieksekusi dengan hasil yang belum siap. Perbaikannya, tempatkan await pada setiap pemanggilan yang hasilnya langsung digunakan, atau simpan Future-nya ke variabel untuk ditunggu kemudian.
  2. Menuliskan tipe kembalian tanpa Future pada fungsi async. Tipe kembalian fungsi async harus Future<T>, bukan T, sehingga deklarasi String f() async memicu error kompilasi. Perbaikannya, tulis Future<String> f() async sesuai nilai yang benar-benar dikembalikan.
  3. Menunggu beberapa Future satu per satu. Pola await a(); await b(); menjalankan keduanya berurutan, sehingga total waktunya menjadi jumlah keduanya. Apabila keduanya tidak saling bergantung, gunakan Future.wait agar berjalan bersamaan.

Ringkasan#

  • Future mewakili nilai yang tiba kemudian, dan fungsi async membungkus nilai kembaliannya menjadi Future bagi pemanggilnya.
  • await menunda kelanjutan fungsi asinkron tanpa membekukan aplikasi, sehingga urutan keluaran mencerminkan kerja event loop.
  • Error asinkron ditangkap dengan try-catch di sekitar await, dan finally menjamin pekerjaan pembersihan.
  • Future.wait menunggu beberapa Future sekaligus dan mengembalikan hasilnya terurut sesuai argumennya.

Referensi#