npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@ggoodman/context

v2.1.0

Published

A take on ergonomic cancellation and timeout propagation in JavaScript inspired by the Go context package

Downloads

8

Readme

@ggoodman/context

npm (scoped) NPM

A take on ergonomic cancellation and timeout propagation in JavaScript inspired by the Go context package.

In long-lived services and processes, it is difficult to express and manage the different lifetimes of different operations. Typically, at the root there is the process itself. The overall process may be designed to run to completion or indefinitely. In either case, such processes typically can be interrupted by things like OS signals (ie: SIGINT / SIGTERM) and may want to trigger graceful shutdown in such cases.

These long-lived processes are often responding to events like http requests or changes on the file system. In the former case, it is nice to be able to enforce deadlines on the handling of a request. In the latter case, subsequent events may invalidate any outstanding operations. The Context API is designed to help in these situations by making it easy to represent a hierarchy of nested operations with their own lifetimes.

At a high level, the lifetime of a child Context will never exceed that of its parent. That means if an ancestor Context is cancelled--either explicitly via a cancel function or implicitly by timing out--all of its descendents will themselves be cancelled.

Someone designing a long-lived process that responds to events may want to represent the lifecycle of the overall process as a parent Context that gets cancelled upon certain signals. The operations triggered by the events this process observes might be associated with child Context. In some cases, these child contexts might be created with internal dealines or timeouts. In others, new events may invalidate prior operations in which case explicit cancellation might be useful.

For more insight on the Go package that inspires this module, please see their introductory blog post.

Installation

npm install --save @ggoodman/context

Example

import { Background, withCancel, withEventEmitter, withTimeout } from '@ggoodman/context';
import Express from 'express';
import * as Stream from 'stream';

const app = Express();

// Wire up some signal handlers for some process-level events that would indicate a need to
// shut down the service.
const appContext = withEventEmitter(
  Background(),
  process,
  ['SIGINT', 'SIGTERM', 'uncaughtException', 'unhandledRejection'],
  (eventName) => new Error(`Received event ${eventName}`)
);

// Trigger graceful shutdown at the earliest cancellation signal
appContext.onDidCancel(() => app.close());

// Let's create a middleware to make a child context for each request.
// When either the app context or the request is closed, this context and any
// child contexts thereof will close. This let's us stop any expensive operations
// associated with aborted or failed requests.
app.use((req, res, next) => {
  // Create the child context and attach it to the request. The child context will
  // become cancelled if the request is aborted or the connection is closed. It will
  // also become cancelled if the overall app context is cancelled.
  const { ctx, cancel } = withTimeout(appContext, 5000);

  // Use the finished helper to listen for errors or completion of the
  // response and cancel accordingly.
  Stream.finished(res, cancel);

  ctx.onDidCancel((reason) => {
    if (isDeadlineExceededError(reason) && !res.headersSent) {
      // If the request-specific timeout gets triggered and we have yet to serve
      // this request, we'll serve a 504 error.
      res.writeHead(504);
      res.end();
    }
  });

  // Stuff the context on to the req object for lack of a simpler to document
  // approach.
  req.ctx = ctx;

  return next();
});

app.get('/', (req, res, next) => {
  // We're going to perform some expensive operation and pass this request's context
  // to that call. That way, the expensive call can be aborted if the context is
  // cancelled.
  return performExpensiveOperation(req.ctx).then((result) => res.end(result), next);
});

app.listen(0);

API

Exports

  • Background(): A function that returns the top-level Context instance that can never be cancelled but from which all application- and library-level Contexts are derived.

  • isContext(obj): Returns a boolean value based on whether the supplied obj is an instance of Context. Also acts as a TypeScript type guard.

  • isCancelledError(err): Returns a boolean value based on whether the supplied err is an instance of CancelledError. Also acts as a TypeScript type guard.

  • isContextError(err): Returns a boolean value based on whether the supplied err is an instance of either CancelledError or DeadlineExceededError. Also acts as a TypeScript type guard.

  • isDeadlineExceededError(err): Returns a boolean value based on whether the supplied err is an instance of DeadlineExceededError. Also acts as a TypeScript type guard.

  • withCancel(ctx): Create a child Context and a method to cancel that context where:

    Returns an object with the shape:

    • cancel(reason) is a function that will cancel the child context where:
      • reason is an optional value that will be propagated to onDidCancel handlers and will become the child Context's cancellationReason.
    • ctx is the child Context object.
  • withEventEmitter(ctx, ee, eventNames, reasonFactory): Create a child Context that will be cancelled when the event emitter emits any of the supplied events where:

    • ctx is a parent Context instance.
    • ee is a Node.js EventEmitter instance.
    • eventNames is a single string event name or an array of string event names. The firing of the first event among those supplied will cause the returned Context instance to be cancelled.
    • reasonFactory is an optional function that accepts the the eventName and any other arguments passed to the emitter's handler. It should return a string reason explaining the cancellation.
  • withTimeout(ctx, timeoutMs): A function that will return a child Context that will automatically be cancelled after the supplied timeout and a method to cancel that context where:

    • ctx is a parent Context instance.

    • timeoutMs is an interval in milliseconds after which point the returned Context should be cancelled.

    Returns an object having a cancel method and ctx instance, similar to withCancel().

    A context that gets cancelled due to it timing out will have a cancellationReason that is an instance of DeadlineExceededError.

  • withDeadline(ctx, epochTimeMs): A function that will return a child Context that will automatically be cancelled at the supplied epoch time and a method to cancel that context where:

    • ctx is a parent Context instance.

    • epochTimeMs is a unix timestamp in millisecond resolution at which point the returned Context should be cancelled.

    Returns an object having a cancel method and ctx instance, similar to withCancel().

    A context that gets cancelled due to it passing its deadline will have a cancellationReason that is an instance of DeadlineExceededError.

Context

Context is an interface representing a node in the context hierarchy that may or may not be cancelled. A child context created from a parent context that is already cancelled will itself be created in the cancelled state. Listners registered using onDidCancel on a cancelled context will be fired asynchronously.

  • cancellationReason: The reason for the cancellation, if the context is cancelled.
  • onDidCancel(handler): Register an event handler for when the context is cancelled, returning an object with a dispose() method, where:
    • handler is a function that will be invoked with the cancellation reason if and when the Context is cancelled.

Future work

  • Provide toAbortSignal and wireAbortSignal helpers to provide interoperability with AbortController and AbortSignal primitives.
  • Provide a utility function to easily run async logic