Options
All
  • Public
  • Public/Protected
  • All
Menu

with-defer

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:

  • In tests, instead of having shared 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.
  • In complex CLI workflows or build tool logic, this can help dispose of handles that might otherwise keep the event loop open, preventing your app from cleanly exiting.
  • Manage the closing of your server and database connections.
  • ... we'd love to hear what else you can dream up.

Installation

npm install with-defer

Usage

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);
  });
}

Docs

Read the API Documentation online.

Index

Classes

Functions

Functions

runWithDefer

  • runWithDefer<TFunc>(func: TFunc): ReturnedPromise<TFunc>
  • Run a handler function (func) such that any callbacks registered with the defer 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 an AggregateError will be thrown with these errors.

    Type parameters

    • TFunc: FunctionWithCleanup

    Parameters

    • func: TFunc

      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.

    Returns ReturnedPromise<TFunc>

    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);
      });
    }
    

Legend

  • Constructor
  • Property
  • Inherited property

Generated using TypeDoc