Resource management
For automatic cleanup of resources
Resources like database connections, file handles, and locks need cleanup. Traditional approaches are error-prone:
// 🚨 Manual cleanup is easy to forget
const conn = openConnection();
doWork(conn);
conn.close(); // What if doWork throws?
// 🚨 try/finally is verbose and doesn't compose
const conn = openConnection();
try {
doWork(conn);
} finally {
conn.close();
}
The using declaration is a new JavaScript feature that automatically disposes resources when they go out of scope:
const process = () => {
using conn = openConnection();
doWork(conn);
}; // conn is automatically disposed here
This works even if doWork throws—disposal is guaranteed.
Disposable resources
A resource is disposable if it has a [Symbol.dispose] method:
interface Disposable {
[Symbol.dispose](): void;
}
For async cleanup, use [Symbol.asyncDispose] with await using:
interface AsyncDisposable {
[Symbol.asyncDispose](): PromiseLike<void>;
}
Block scopes
Use block scopes to control exactly when resources are disposed:
const createLock = (name: string): Disposable => ({
[Symbol.dispose]: () => {
console.log(`unlock:${name}`);
},
});
const process = () => {
console.log("start");
{
using lock = createLock("a");
console.log("critical-section-a");
} // lock "a" released here
console.log("between");
{
using lock = createLock("b");
console.log("critical-section-b");
} // lock "b" released here
console.log("end");
};
// Output:
// "start"
// "critical-section-a"
// "unlock:a"
// "between"
// "critical-section-b"
// "unlock:b"
// "end"
Combining with Result
Result and Disposable are orthogonal:
- Result answers: "Did the operation succeed?"
- Disposable answers: "When do we clean up resources?"
Early returns from Result checks don't bypass using—disposal is guaranteed on any exit path (see below).
DisposableStack
When acquiring multiple resources, use DisposableStack to ensure all are cleaned up:
const processResources = (): Result<string, CreateResourceError> => {
using stack = new DisposableStack();
const db = createResource("db");
if (!db.ok) return db; // stack disposes nothing yet
stack.use(db.value);
const file = createResource("file");
if (!file.ok) return file; // stack disposes db
stack.use(file.value);
return ok("processed");
}; // stack disposes file, then db (reverse order)
The pattern is simple:
- Create a
DisposableStackwithusing - Try to create a resource (returns
Result) - If failed, return early—stack disposes what's been acquired
- If succeeded, add to stack with
stack.use() - Repeat for additional resources
For async resources, use AsyncDisposableStack with await using.
API overview:
stack.use(resource)— adds a disposable resourcestack.defer(fn)— adds a cleanup function (like Go'sdefer)stack.adopt(value, cleanup)— wraps a non-disposable value with cleanupstack.move()— transfers ownership to caller
The use-and-move pattern
When a factory function creates resources for use elsewhere, use move() to transfer ownership:
interface OpenFiles extends Disposable {
readonly handles: ReadonlyArray<FileHandle>;
}
const openFiles = (
paths: ReadonlyArray<string>,
): Result<OpenFiles, OpenFileError> => {
using stack = new DisposableStack();
const handles: Array<FileHandle> = [];
for (const path of paths) {
const file = open(path);
if (!file.ok) return file; // Error: stack cleans up opened files
stack.use(file.value);
handles.push(file.value);
}
// Success: transfer ownership to caller
const cleanup = stack.move();
return ok({
handles,
[Symbol.dispose]: () => cleanup.dispose(),
});
};
const processFiles = (): Result<void, MyError> => {
const result = openFiles(["a.txt", "b.txt", "c.txt"]);
if (!result.ok) return result;
using files = result.value;
// ... use files.handles ...
return ok();
}; // files cleaned up here
Without move(), the stack would dispose files when openFiles returns—even on success.
Ready to use
TypeScript 5.2+ implements the using keyword, and Evolu polyfills runtime resource management for environments that still need it (Safari and React Native), see polyfills setup.
Learn more
- MDN: Resource management
- MDN: using statement
- MDN: DisposableStack
- MDN: AsyncDisposableStack
Result.test.tsfor comprehensive usage patterns- Resources for reference-counted shared resources with delayed disposal