Because once you've tasted go's
defer
, you can't get enough.
This library provides tools to help you clean up resources in a simple and intuitive way. As you create more than one resource that needs to be disposed in a single, complex flow, you find yourself fighting with deeply-nested try / finally
blocks and a sense of dread. "What happened to the flattened logic that I was promised with async / await
?", you might ask. Fear not, we're here to help.
Using runWithDefer
will allow you to colocate your instructions to free resources with their creation. Some real-world use-cases:
let
variables instantiated in .before
handlers and cleaned up in .after
handlers, wrap your test logic in runWithDefer
and get better typing and cleaner logic.npm install with-defer
Below is a fictitious example that opens a file handle for reading, and writes the contents of that file to another file handle opened for writing. Notice that the instructions to close the file handles are adjacent to the code that opens them. This makes it easy to understand the intent of the code.
Deferred clean-up functions will be run in FIFO order. This means the last registered clean-up function will run first. If a clean-up function returns a Promise
, it will be awaited. Any rejections or thrown exceptions in clean-up functions will NOT prevent others from running. If any clean-up functions throw, these errors will be accumulated and the whole runWithDefer
function will return a rejected Promise
whose value is an AggregateError
of the thrown exceptions.
Note: Yes, I know that there are 'better' ways to achieve the example below in Node.js. The point isn't so much writing one file's contents to another but how these file handles are cleaned up.
import * as Fs from 'fs';
import { runWithDefer } from 'with-defer';
async function main() {
await runWithDefer(async (defer) => {
// Open a file hande and make sure we close it.
const handle1 = await Fs.promises.open('path/to/file', 'r+');
defer(() => handle1.close());
const content = await handle1.readFile('utf8');
// We open a 2nd handle here and register its close function. Notice we don't have to deal
// with crazy nesting of try / catch blocks and can co-locate clean-up with obtaining the
// resource.
const handle2 = await Fs.promises.open('path/to/file2', 'w+');
defer(() => handle2.close());
await handle2.writeFile(content);
});
}
Read the API Documentation online.
Function that will be called with a single defer
argument that is itself a function that an be called to register callbacks that will be called in LIFO whenever the handler completes.
A Promise
whose settled value will match the result of executing the handler func
.
Example:
import * as Fs from "fs";
import { runWithDefer } from "with-defer";
async function main() {
await runWithDefer(async (defer) => {
// Open a file hande and make sure we close it.
const handle1 = await Fs.promises.open("path/to/file", "r+");
defer(() => handle1.close());
const content = await handle1.readFile("utf8");
// We open a 2nd handle here and register its close function. Notice we don't have to deal
// with crazy nesting of try / catch blocks and can co-locate clean-up with obtaining the
// resource.
const handle2 = await Fs.promises.open("path/to/file2", "w+");
defer(() => handle2.close());
await handle2.writeFile(content);
});
}
Generated using TypeDoc
Run a handler function (
func
) such that any callbacks registered with thedefer
argument passed to it will be called in LIFO order when the function completes.This is conceptually similar to the
defer
statement in Go in that you can use it to register resources for disposal as they are created. Each deferred callback will be await in LIFO order. Any errors thrown while calling these deferred callbacks will be collected without preventing other callback from executing. If any errors are thrown anAggregateError
will be thrown with these errors.