This episode dissects asynchronous programming in Dart: Future with async and await, error handling, the basics of Stream with listeners and transformations, the Isolate model for concurrency, and asynchronous I/O use cases in real applications.

Modern applications can't wait: reading files, making HTTP calls, or waiting for socket connections must run without freezing the interface. Dart solves this with an elegant asynchronous single-threaded model — you write code that looks synchronous, but runs without blocking.
Episode 7 dissects the three pillars of asynchronous programming in Dart: Future, Stream, and Isolate. You'll learn to write and combine async code with async and await, handle errors, transform data flows, and move heavy work to isolates.
After this episode, you'll be confident writing servers, network clients, and responsive applications.
A Future represents a computation whose result becomes available later:
Future<String> ambilData() async {
await Future.delayed(Duration(seconds: 1));
return 'data dari jaringan';
}
void main() async {
var data = await ambilData();
print(data);
}await ambilData() suspends the execution of the main function without blocking the isolate. While waiting, the runtime can run other code — this is why a single thread can serve many I/O operations.
Async still needs error handling. Use try and catch as usual:
Future<void> cekKoneksi() async {
await Future.error(Exception('gagal koneksi'));
}
void main() async {
try {
await cekKoneksi();
} catch (e) {
print('Terjadi error: $e');
}
}await Future.error(...) throws an error caught by the catch block. This pattern is the same as synchronous error handling, so the mental transition is smooth.
Unlike a Future, which produces a single value, a Stream produces many values over time:
import 'dart:async';
void main() {
var controller = StreamController<int>();
controller.stream.listen((data) {
print('Terima: $data');
});
controller.add(1);
controller.add(2);
controller.close();
}controller.stream.listen(...) registers a listener that's called every time a new value arrives. This pattern is used for events, progress bars, and real-time updates.
Streams can be transformed with methods like map, where, and take:
import 'dart:async';
void main() async {
var stream = Stream.fromIterable([1, 2, 3, 4, 5]);
var genap = stream.where((n) => n.isEven).map((n) => n * 10);
await for (var nilai in genap) {
print(nilai);
}
}await for (var nilai in genap) consumes stream values sequentially. The combination of where and map builds a declarative data transformation pipeline.
For CPU-heavy work like large parses or cryptography, don't block the main isolate. Move it to another isolate — an independent execution unit that shares messages, not memory:
import 'dart:isolate';
void hitung(int data) {
var total = 0;
for (var i = 1; i <= data; i++) {
total += i;
}
print('Hasil: $total');
}
void main() async {
await Isolate.spawn(hitung, 1000000);
print('Isolate utama tetap responsif');
}Isolate.spawn(hitung, 1000000) runs the hitung function in a new isolate. Since isolates don't share memory, there's no race condition — communication happens only through ports and messages.
Asynchronous I/O is the backbone of server and client applications. For example, reading a file without blocking:
import 'dart:io';
Future<void> main() async {
var file = File('catatan.txt');
var isi = await file.readAsString();
print(isi);
}file.readAsString() returns a Future that completes when the entire content is read. For large files, use file.openRead(), which returns a per-chunk Stream. The same pattern applies to HTTP, databases, and sockets.
Tip
If you often write await inside a sequential loop, consider Future.wait to run independent tasks in parallel and save time.
Key takeaways:
Future represents a single value in the future; async and await make it readable.try and catch just like synchronous code.Stream produces many values over time; listen to consume, where and map to transform.await for consumes stream values sequentially.Isolate executes heavy work without sharing memory with the main isolate.readAsString, HTTP, and databases all follow the same async pattern.In the next episode 8, we'll cover package and dependency management — writing Dart packages, structuring pubspec.yaml, the difference between public and local packages, the pub.dev ecosystem with versioning, and using dart pub get and dart pub upgrade.