This is a bare-bones implementation of the interfaces AsyncDisposableStack
and DisposableStack
from the Explicit Resource Management Proposal.
Note: see the examples below for more detailed usage examples.
import {
Disposable,
DisposableStack,
} from "https://gist.githubusercontent.com/nberlette/678f2bac2b87b710586d11e9891673f1/raw/mod.ts";
import {
AsyncDisposable,
AsyncDisposableStack,
} from "https://gist.githubusercontent.com/nberlette/678f2bac2b87b710586d11e9891673f1/raw/mod.ts";
If you're in an environment that doesn't yet support the Symbol.dispose
and
Symbol.asyncDispose
symbols, you can use the polyfill provided in this gist
to add them to the global Symbol
object. It includes global type definitions
that are augmented into the SymbolConstructor
interface.
import "https://gist.githubusercontent.com/nberlette/678f2bac2b87b710586d11e9891673f1/raw/symbol.ts";
Note: You have to explicitly import the
./symbol.ts
file, however, as it is not exported from the./mod.ts
file.
This is to prevent type collisions in environments that already support the proposed symbol. There can only be one
unique symbol
for a given name in any scope (that's what makes them unique!)
Here's a simple example of how one might use the AsyncDisposableStack
API.
You can drop this code into the Deno CLI and it should "just work".
import {
AsyncDisposable,
AsyncDisposableStack,
} from "https://gist.githubusercontent.com/nberlette/678f2bac2b87b710586d11e9891673f1/raw/mod.ts";
class AsyncConstruct implements AsyncDisposable {
#resourceA: AsyncDisposable;
#resourceB: AsyncDisposable;
#resources: AsyncDisposableStack;
get resourceA() { return this.#resourceA; }
get resourceB() { return this.#resourceB; }
async init(): Promise<void> {
// stack will be disposed when exiting this method for any reason
await using stack = new AsyncDisposableStack();
// adopts an async resource, adding it to the stack. this lets us utilize
// resource management APIs with existing features that may not support the
// bleeding-edge features like `AsyncDisposable` yet. In this case, we're
// adding a temporary file (as a string), with a removal function that will
// clean up the file when the stack is disposed (or this function exits).
this.#resourceA = await stack.adopt(await Deno.makeTempFile(), async (path) => await Deno.remove(path));
// do some work with the resource
await Deno.writeTextFile(this.#resourceA, JSON.stringify({ foo: "bar" }));
// Acquire a second resource. If this fails, both `stack` and `#resourceA`
// will be disposed. Notice we use the `.use` method here, since we're
// acquiring a resource that implements the `AsyncDisposable` interface.
this.#resourceB = await stack.use(await this.get());
// all operations succeeded, move resources out of `stack` so that they aren't disposed
// when this function exits. we can now use the resources as we please, and
// they will be disposed when the parent object is disposed.
this.#resources = stack.move();
}
async get(): Promise<AsyncDisposable> {
console.log("🅱️ acquiring resource B")
const resource = { data: JSON.parse(await Deno.readTextFile(this.#resourceA)) };
return Object.create({
async [Symbol.asyncDispose]() {
console.log("🅱️ disposing resource B")
resource.data = null!;
return await Promise.resolve();
},
}, { resource: { value: resource, enumerable: true } });
}
async disposeAsync() {
await this.#resources.disposeAsync();
}
async [Symbol.asyncDispose]() {
await this.#resources.disposeAsync();
}
}
{
await using construct = new AsyncConstruct();
await construct.init();
console.log("🅰️ resource A:", construct.resourceA);
console.log("🅱️ resource B:", construct.resourceB);
console.log("We're done here.");
}
class Construct implements Disposable {
#resourceA: Disposable;
#resourceB: Disposable;
#resources: DisposableStack;
constructor() {
// stack will be disposed when exiting constructor for any reason
using stack = new DisposableStack();
// get first resource
this.#resourceA = stack.use(getResource1());
// get second resource. If this fails, both `stack` and `#resourceA` will be disposed.
this.#resourceB = stack.use(getResource2());
// all operations succeeded, move resources out of `stack` so that they aren't disposed
// when constructor exits
this.#resources = stack.move();
}
[Symbol.dispose]() {
this.#resources.dispose();
}
}