Operasi seperti membaca berkas, memanggil jaringan, atau sekadar menunggu jeda tidak memberikan nilainya seketika, sehingga kode yang menunggu tidak boleh menghentikan seluruh program. Dart memodelkan keadaan tersebut dengan Future, yaitu objek yang mewakili satu nilai yang tersedia di kemudian hari beserta kegagalan yang mungkin menyertainya. Topik ini membahas cara membuat Future melalui Future.value, Future.delayed, dan Completer, cara menerima nilainya dengan then, cara menangkap kegagalannya dengan catchError, serta cara menunggu banyak Future sekaligus dengan Future.wait. Semua contoh dapat dijalankan di terminal tanpa package tambahan karena Future berada di dart:async yang sudah bawaan.
Future sebagai nilai yang datang kemudian#
Sebuah Future<T> adalah tempat penyimpanan untuk satu nilai bertipe T yang belum tersedia pada saat objeknya dibuat. Fungsi yang pekerjaannya tertunda mengembalikan Future<T>, sedangkan pemanggil mendaftarkan penerima hasil melalui then. Pendaftaran itu tidak menunggu, sehingga baris kode setelah pemanggilan tetap dijalankan lebih dahulu, dan callback pada then baru dieksekusi ketika nilainya tiba. Urutan inilah yang membedakan kode asinkron dari kode sinkron biasa.
Future<int> nilaiKemudian() {
return Future.value(10);
}
void main() {
print("program dimulai");
nilaiKemudian().then((nilai) {
print("nilai tiba: $nilai");
});
print("baris ini dicetak lebih dahulu");
}# menjalankan program dari folder topik
dart run pertama.dartprogram dimulai
baris ini dicetak lebih dahulu
nilai tiba: 10Fungsi nilaiKemudian menggunakan Future.value(10), yaitu Future yang sudah lengkap sejak awal dengan nilai 10. Meskipun begitu, callback pada then tetap tidak dijalankan di tengah pemanggilan, melainkan setelah seluruh kode sinkron pada fungsi main selesai. Keluaran menunjukkan urutan tersebut dengan jelas, sehingga kesimpulannya dapat dirumuskan: then mendaftarkan reaksi atas hasil, bukan permintaan untuk menunggu di tempat. Perilaku ini berlaku sama untuk Future yang sudah lengkap maupun Future yang baru akan terisi.
Membuat Future dengan jeda#
Future.delayed membuat Future yang nilai barunya muncul setelah durasi tertentu, sehingga contoh latihan dapat menyimulasikan operasi lambat tanpa jaringan atau berkas sungguhan. Argumen pertamanya berupa Duration, sedangkan argumen kedua berupa fungsi yang menghasilkan nilai ketika jeda selesai. Bentuk lain yang sering digunakan adalah Future(computation), yaitu Future yang menjalankan fungsi komputasinya di antrean event, sehingga pemanggil tetap mendapat alur kendali lebih dahulu. Nilai const pada Duration menyusun durasi pada saat kompilasi, sehingga penulisan const Duration(...) menjadi kebiasaan yang baik untuk jeda yang tetap. Satuan lain seperti seconds dan minutes juga tersedia apabila contoh latihan membutuhkannya, dan pola kodenya tidak berubah antarsatuan.
void main() {
print("permintaan dikirim");
Future.delayed(const Duration(milliseconds: 50), () {
return 24;
}).then((nilai) {
print("nilai tiba: $nilai");
});
print("program melanjutkan pekerjaan lain");
}# menjalankan program dari folder topik
dart run tertunda.dartpermintaan dikirim
program melanjutkan pekerjaan lain
nilai tiba: 24Program mengirim permintaan, melanjutkan pekerjaan sinkronnya, dan baru mencetak nilai ketika jeda 50 milidetik habis. Selama jeda tersebut tidak ada satu baris kode pun yang tertahan, karena then hanya mendaftarkan penerima hasil. Pola ini adalah model dasar semua operasi asinkron di Dart, mulai dari pembacaan berkas pada dart:io hingga permintaan HTTP, yang semuanya mengembalikan Future dengan mekanisme yang sama.
Menangkap kegagalan dengan catchError#
Sebuah Future dapat berakhir dengan error, bukan dengan nilai, apabila pekerjaan di baliknya gagal. Error semacam itu tidak dimunculkan sebagai exception biasa di baris pemanggilan, melainkan dibawa oleh Future itu sendiri dan diteruskan ke catchError yang terdaftar. Apabila tidak ada catchError maupun penanganan lain, error tersebut berakhir sebagai unhandled exception yang melaporkan dirinya pada saat program berjalan. Penempatan catchError setelah rantai then membuat satu penanganan melayani seluruh langkah di depannya. Callback pada catchError boleh pula mengembalikan nilai pengganti, sehingga Future hasil rantai berakhir normal dengan nilai tersebut apabila penanganannya menemukan cara keluar.
Future<int> uraikan(String teks) {
return Future(() => int.parse(teks));
}
void main() {
uraikan("42").then((nilai) {
print("berhasil: $nilai");
}).catchError((e) {
print("gagal diuraikan: ${e.runtimeType}");
});
uraikan("dua").then((nilai) {
print("berhasil: $nilai");
}).catchError((e) {
print("gagal diuraikan: ${e.runtimeType}");
});
print("pemrosesan masukan dimulai");
}# menjalankan program dari folder topik
dart run gagal.dartpemrosesan masukan dimulai
berhasil: 42
gagal diuraikan: FormatExceptionKedua pemanggilan uraikan dilakukan berurutan, dan keduanya selesai secara asinkron sebelum program berakhir. Masukan "42" menghasilkan jalur sukses pada then, sedangkan masukan "dua" membuat int.parse melempar FormatException yang dibawa Future dan jatuh ke catchError. Perhatikan bahwa pesan pemrosesan masukan dimulai dicetak paling awal, karena seluruh pendaftaran callback berlangsung seketika tanpa menunggu hasil apa pun. Dengan pendaftaran seketika tersebut, tidak ada celah waktu ketika error lewat tanpa penerima.
Completer untuk Future yang diisi manual#
Completer menyediakan cara membuat Future yang penyelesaiannya berada di tangan penulis kode, bukan di fungsi pabrikan seperti Future.delayed. Objek Completer<T> membagi satu pekerjaan menjadi dua sisi, yaitu sisi konsumen yang memegang completer.future dan sisi produsen yang memanggil complete untuk mengisinya. Pola ini berguna ketika nilai berasal dari sumber yang tidak berbentuk Future sejak awal, misalnya pesan dari pendengar atau hasil callback pustaka lama. Dengan Completer, kejadian yang berbasis callback dapat dibungkus menjadi Future yang gaya penggunaannya seragam. Setiap Completer hanya boleh diselesaikan satu kali, karena pemanggilan complete yang berulang memunculkan StateError.
import 'dart:async';
void tampilkanHasil(Future<double> hasil) {
hasil.then((suhu) {
print("suhu tercatat: $suhu");
});
}
void main() {
final completer = Completer<double>();
tampilkanHasil(completer.future);
print("sensor diaktifkan, menunggu data");
Future.delayed(const Duration(milliseconds: 50), () {
completer.complete(21.5);
});
}# menjalankan program dari folder topik
dart run completer.dartsensor diaktifkan, menunggu data
suhu tercatat: 21.5Fungsi tampilkanHasil hanya menerima sebuah Future<double> dan tidak mengetahui bagaimana nilainya diisi, sedangkan bagian main mengisinya 50 milidetik kemudian melalui completer.complete(21.5). Kedua sisi tersebut terhubung tanpa saling mengenal detail mekanismenya, karena satu-satunya kontrak di antara keduanya adalah Future yang dibagikan. Apabila kegagalan perlu disampaikan, completer.completeError mengisi Future dengan error yang berakhir di catchError milik konsumen, sebagaimana error dari pekerjaan asinkron biasa.
Menunggu banyak Future dengan Future.wait#
Ketika pekerjaan yang tertunda lebih dari satu, Future.wait menerima daftar Future dan menghasilkan satu Future baru yang berisi list hasil dalam urutan daftarnya. Future gabungan itu lengkap setelah seluruh anggotanya selesai, sehingga hasil yang datang lebih cepat tetap menunggu yang lebih lambat sebelum keduanya diserahkan bersama. Seluruh anggota daftarnya sudah berjalan sejak daftar itu dibentuk, sehingga Future.wait benar-benar menunggu dan tidak memulai pekerjaan apa pun. Apabila salah satu anggota berakhir dengan error, Future gabungan ikut berakhir dengan error tersebut, dan penanganannya tetap berada di catchError. Pola ini menjadi dasar menjalankan banyak permintaan secara bersamaan sebelum dipadukan menjadi satu hasil.
Future<int> pengukuran(int kelipatan, int nilai) {
return Future.delayed(Duration(milliseconds: kelipatan * 50), () => nilai);
}
void main() {
final kumpulan = Future.wait([
pengukuran(3, 10),
pengukuran(2, 24),
pengukuran(1, 8),
]);
kumpulan.then((hasil) {
print("semua pengukuran selesai");
print("hasil: $hasil");
});
}# menjalankan program dari folder topik
dart run menunggu-semua.dartsemua pengukuran selesai
hasil: [10, 24, 8]Tiga pengukuran sengaja diberi jeda berbeda, yaitu 150, 100, dan 50 milidetik, sehingga yang paling lambat justru yang muncul pertama di daftar. Keluaran tetap menampilkan [10, 24, 8] karena Future.wait menyusun hasil menurut urutan daftar masukannya, bukan menurut urutan penyelesaian. Jaminan urutan seperti ini membuat hasil gabungan langsung dapat dipasangkan dengan daftar permintaannya tanpa pencocokan ulang.
Ringkasan#
Future<T>mewakili satu nilai bertipeTyang tersedia di kemudian hari, danthenmendaftarkan penerima nilainya tanpa menahan alur kode.Future.value,Future.delayed, danFuture(computation)membuat Future dari nilai jadi, jeda waktu, dan komputasi yang ditunda.- Error pada Future dibawa sampai
catchError, dan error tanpa penanganan berakhir sebagai unhandled exception. Completermemisahkan sisi konsumen yang memegangcompleter.futuredari sisi produsen yang memanggilcomplete.Future.waitmenunggu banyak Future sekaligus dan menyusun hasil menurut urutan daftarnya, bukan urutan penyelesaiannya.
Referensi#
- Concurrency in Dart — dart.dev — Dokumentasi resmi Future, event loop, dan cara Dart menjadwalkan pekerjaan asinkron.
- Future class — dart.dev
— Referensi API
Future, termasukFuture.value,Future.delayed, danFuture.wait. - Completer class — dart.dev
— Referensi API
CompleterbesertacompletedancompleteError.