Ever had a multi-step async init where step 5 fails and you need to clean up steps 1–4 in reverse order — even if some cleanups throw too? Nested try/catch/finally turns into an unreadable mess fast.
Other languages solved this: Go has
defer, C++ has RAII, C# has using. Dart has none of these. But you can build the equivalent in 6 lines:void Function() dispose = () { dispose = () {}; };
void disposable(void Function() fn) {
final prev = dispose;
dispose = () {
try { fn(); } finally { prev(); }
};
}Each
disposable() call wraps the previous dispose into a closure chain — a linked list of cleanups in LIFO order. try/finally guarantees every cleanup runs even if one throws. The first line is double-dispose protection.Usage is dead simple:
final db = await openDatabase();
disposable(() => db.close());
final ws = await WebSocket.connect(url);
disposable(() => ws.close());
// If anything fails here — dispose()
// unwinds: ws.close() → db.close()
Works great with a
CancellationToken — just cancel.addListener(dispose) before the streaming phase, and user cancel tears down exactly what was initialized.The async variant looks identical, just add
async/await. Extract into a mixin for reuse.Full article with detailed examples, cancellation pattern, and edge cases
#TipOfTheDay #Dart #Flutter #plugfox #cleanup