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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@theateros/futur

v0.0.1

Published

<p align="center"> <img src="../../.etc/assets/futur-logo.webp" alt="Theater OS - Foundations - Futur"> </p>

Readme

Theater OS - Futur

A lazy, type-safe asynchronous computation wrapper for TypeScript that combines the power of Promises with Result-based error handling, providing deferred execution and built-in cancellation support.

Why Futur?

JavaScript Promises are eager - they start executing immediately upon creation. While this works for many use cases, it has limitations:

  • Eager execution: Promises run immediately, making it hard to control when async operations start
  • No type-safe errors: Rejected promises lose type information about the error
  • No built-in cancellation: AbortController must be managed separately
  • Exception-based errors: You need try-catch blocks to handle rejections

Futur addresses these issues by providing:

  • Lazy execution: The async operation only runs when you await the Futur
  • Type-safe errors: Returns Result<T, E> with typed success and error values
  • Built-in cancellation: Every Futur runner receives a special FuturAbortion instance for cancellation control
  • No exceptions: Errors are captured as Result.err values, not thrown
  • Promise interoperability: Works with async/await, Promise.all, Promise.race, etc.

Installation

npm install @theateros/futur

Getting Started

Basic Usage

import { Futur } from "@theateros/futur";
import { Result } from "@theateros/result";

// Create a Futur - it won't run until awaited
const futur = Futur.of<string, Error>(({ resolve }) => {
  resolve("Hello, World!");
});

// Run the Futur and get a Result
const result = await futur;

if (Result.isOk(result)) {
  console.log(result.value); // "Hello, World!"
}

Creating Futurs

Use Futur.of to create a Futur from a runner function:

import { Futur } from "@theateros/futur";
import { Result } from "@theateros/result";

// Success case
const successFutur = Futur.of<number, string>(({ resolve }) => {
  resolve(42);
});

// Error case
const errorFutur = Futur.of<number, string>(({ reject }) => {
  reject("Something went wrong");
});

// Async operations
const asyncFutur = Futur.of<string, Error>(({ resolve }) => {
  setTimeout(() => resolve("Delayed result"), 1000);
});

// All results are type-safe
const result = await successFutur;
if (Result.isOk(result)) {
  console.log(result.value); // 42
}

const errorResult = await errorFutur;
if (Result.isErr(errorResult)) {
  console.log(errorResult.error); // "Something went wrong"
}

Wrapping Existing Promises

Use Futur.ofPromise to wrap existing Promise-returning functions:

import { Futur } from "@theateros/futur";
import { Result } from "@theateros/result";

// Let's take some standard async operation
async function stdFetchUser(id: string): Record<string, string> {
  const response = await fetch(`/api/users/${id}`);

  const data = await response.json();

  return data as Record<string, string>;
}

// Wrap a fetch call
const fetchUser = (id: string) => Futur.ofPromise(() => stdFetchUser(id));

const result = await fetchUser(1);

if (Result.isOk(result)) {
  console.log("User:", result.value);
} else {
  console.log("Error:", result.error);
}

Error Transformation

Transform errors using the optional catcher parameter:

import { Futur } from "@theateros/futur";
import { Result } from "@theateros/result";

interface ApiError {
  code: string;
  message: string;
}

const fetchData = Futur.ofPromise(
  () => fetch("/api/data").then((res) => res.json()),
  (error) =>
    ({
      code: "FETCH_ERROR",
      message: String(error),
    }) as ApiError,
);

const result = await fetchData;

if (Result.isErr(result)) {
  // error is typed as ApiError
  console.log(`Error ${result.error.code}: ${result.error.message}`);
}

Cancellation

Every Futur has built-in cancellation support. When aborted, the Futur rejects with an AbortedFailure.

Aborting from Within the Runner

The runner receives an abortion object in its payload, which provides access to cancellation methods and the abort controller:

import { Futur } from "@theateros/futur";
import { Result } from "@theateros/result";

const fetchWithAbort = Futur.of<Response, Error>(({ resolve, reject, abortion }) => {
  // Use the provided abort controller with fetch
  fetch("/api/data", { signal: abortion.controller.signal }).then(resolve).catch(reject);
});

const result = await fetchWithAbort;

You can also abort directly from within the runner:

import { Futur } from "@theateros/futur";
import { Result } from "@theateros/result";

const conditionalFutur = Futur.of<string, never>(({ resolve, abortion }) => {
  // Check some condition and abort if needed
  if (someCondition) {
    abortion.abort();
    return;
  }

  resolve("success");
});

The abortion object provides:

  • abort(): Abort the Futur operation
  • isAborted(): Check if the Futur has been aborted
  • onAbort(callback): Register a callback for abort events
  • controller: The AbortController instance for use with fetch, streams, etc.

Aborting from Outside

Use the abort() method to cancel a Futur from outside:

import { Futur, AbortedFailure } from "@theateros/futur";
import { Result } from "@theateros/result";
import { Failure } from "@theateros/failure";

const longRunningTask = Futur.of<string, never>(({ resolve }) => {
  setTimeout(() => resolve("completed"), 10000);
});

// Cancel after 1 second
setTimeout(() => {
  longRunningTask.abort();
}, 1000);

const result = await longRunningTask;

if (Result.isErr(result)) {
  if (Failure.isNamed(result.error, "AbortedFailure")) {
    console.log("Task was cancelled:", result.error.message);
    // "Task was cancelled: Futur has been aborted"
  }
}

Checking Abort Status

Use the aborted getter to check if a Futur has been aborted:

import { Futur } from "@theateros/futur";

const futur = Futur.of<string, never>(({ resolve }) => {
  setTimeout(() => resolve("done"), 1000);
});

const promise = futur.then();

// Check if aborted
console.log(futur.aborted); // false

futur.abort();

// Note: aborted may be false after cleanup, check the result instead
const result = await promise;

Handling AbortedFailure

When a Futur is aborted, it rejects with an AbortedFailure:

import { Futur, AbortedFailure } from "@theateros/futur";
import { Result } from "@theateros/result";
import { Failure } from "@theateros/failure";

const futur = Futur.of<string, Error>(({ resolve, abortion }) => {
  // Simulate cancellation
  abortion.abort();
});

const result = await futur;

if (Result.isErr(result)) {
  // Check if it was aborted
  if (result.error instanceof AbortedFailure) {
    console.log("Operation was aborted");
  }

  // Or use Failure.isNamed
  if (Failure.isNamed(result.error, "AbortedFailure")) {
    console.log("Operation was aborted");
  }
}

Re-running After Abort

A Futur creates a fresh execution context on each run, so you can re-run a Futur after it was aborted:

import { Futur, AbortedFailure } from "@theateros/futur";
import { Result } from "@theateros/result";

let attempt = 0;

const retryableFutur = Futur.of<string, never>(({ resolve, abortion }) => {
  attempt++;
  if (attempt === 1) {
    abortion.abort(); // Abort first attempt
  } else {
    resolve(`Success on attempt ${attempt}`);
  }
});

// First run - aborted
const result1 = await retryableFutur;
console.log(Result.isErr(result1)); // true

// Second run - succeeds with fresh execution context
const result2 = await retryableFutur;
if (Result.isOk(result2)) {
  console.log(result2.value); // "Success on attempt 2"
}

Listening to Abort Events

Use onAbort() to register callbacks that will be called when the Futur is aborted. You can use this from within the runner or from outside:

import { Futur } from "@theateros/futur";

// From within the runner
const futur = Futur.of<string, never>(({ resolve, abortion }) => {
  // Register abort callback from within the runner
  abortion.onAbort(() => {
    console.log("Futur was aborted from within!");
  });

  setTimeout(() => resolve("done"), 1000);
});

// Or from outside
const promise = futur.then();

// Register abort callback from outside
const removeCallback = futur.onAbort(() => {
  console.log("Futur was aborted from outside!");
});

// Abort the Futur
futur.abort();

// Remove the callback if needed
removeCallback();

await promise;

Deferred Execution

Futurs are lazy - they only execute when awaited:

import { Futur } from "@theateros/futur";

// This does NOT start the operation
const futur = Futur.of<string, never>(({ resolve }) => {
  console.log("Running!");
  resolve("done");
});

console.log("Futur created");

// This starts the operation
const result = await futur;
// Output:
// "Futur created"
// "Running!"

Promise Interoperability

Futurs work seamlessly with Promise utilities:

import { Futur } from "@theateros/futur";
import { Result } from "@theateros/result";

const futur1 = Futur.of<number, never>(({ resolve }) => resolve(1));
const futur2 = Futur.of<number, never>(({ resolve }) => resolve(2));
const futur3 = Futur.of<number, never>(({ resolve }) => resolve(3));

// Use with Promise.all
const results = await Promise.all([futur1, futur2, futur3]);
// results is Result<number, never>[]

// Use with Promise.race
const slowFutur = Futur.of<string, never>(({ resolve }) => {
  setTimeout(() => resolve("slow"), 1000);
});

const fastFutur = Futur.of<string, never>(({ resolve }) => {
  resolve("fast");
});

const winner = await Promise.race([slowFutur, fastFutur]);
// winner.value === 'fast'

Transforming Results

Use the then callback to transform results:

import { Futur } from "@theateros/futur";
import { Result } from "@theateros/result";

const futur = Futur.of<number, string>(({ resolve }) => {
  resolve(21);
});

// Transform the result
const doubled = await futur.then((result) => {
  if (Result.isOk(result)) {
    return result.value * 2;
  }
  return 0;
});

console.log(doubled); // 42

API Reference

Futur Class

The main class that implements PromiseLike<Result<T, E | AbortedFailure>>.

Type Parameters

  • T: The success value type
  • E: The error value type

Static Methods

  • Futur.of<T, E>(runner: FuturRunner): Futur<T, E>

    Creates a new Futur from a runner function. The runner receives a payload with resolve, reject, and abortion.

    const futur = Futur.of<string, Error>(({ resolve, reject, abortion }) => {
      // Your async logic here
      resolve("success");
      // or: reject(new Error('failure'))
      // Use abortion.abort() to cancel from within the runner
      // Use abortion.controller.signal with fetch, streams, etc.
    });
  • Futur.ofPromise<P, E>(launcher: () => P, catcher?: (error: unknown) => E): Futur<Awaited<P>, E>

    Creates a new Futur from a Promise-returning function. Optionally transform errors with the catcher parameter.

    const futur = Futur.ofPromise(
      () => fetch("/api/data"),
      (error) => ({ code: "ERROR", message: String(error) }),
    );

Instance Methods

  • then<TResult1>(onfulfilled?): Promise<TResult1>

    Runs the Futur and returns a Promise. Called automatically when using await.

  • abort(): void

    Aborts all active executions of the Futur. When aborted, the Futur rejects with an AbortedFailure.

    const futur = Futur.of<string, never>(({ resolve }) => {
      setTimeout(() => resolve("done"), 5000);
    });
    
    // Cancel after 1 second
    setTimeout(() => futur.abort(), 1000);
    
    const result = await futur; // Result.err(AbortedFailure)
  • get aborted: boolean

    Returns true if any active execution of the Futur has been aborted, false otherwise.

    const futur = Futur.of<string, never>(({ resolve }) => {
      setTimeout(() => resolve("done"), 1000);
    });
    
    const promise = futur.then();
    console.log(futur.aborted); // false
    
    futur.abort();
    // Note: aborted may be false after cleanup, check the result instead
  • onAbort(callback: () => void): () => void

    Registers a callback that will be called when the Futur is aborted. Returns a function to remove the callback.

    const futur = Futur.of<string, never>(({ resolve }) => {
      setTimeout(() => resolve("done"), 1000);
    });
    
    const promise = futur.then();
    
    const removeCallback = futur.onAbort(() => {
      console.log("Aborted!");
    });
    
    futur.abort(); // Calls the callback
    
    // Remove the callback
    removeCallback();
    
    await promise;

AbortedFailure Class

A named Failure class used when a Futur is aborted. Extends Failure from @theateros/failure.

import { AbortedFailure } from "@theateros/futur";
import { Failure } from "@theateros/failure";

const failure = new AbortedFailure("Operation cancelled");

// Type checking
failure instanceof AbortedFailure; // true
failure instanceof Failure; // true
Failure.isNamed(failure, "AbortedFailure"); // true

Types

FuturPayload

The payload passed to the runner function:

type FuturPayload = {
  resolve: <V>(value: V) => void;
  reject: <E>(reason: E) => void;
  abortion: FuturAbortion;
};

FuturAbortion

The abortion API provided to the runner:

type FuturAbortion = Readonly<{
  abort: () => void;
  isAborted: () => boolean;
  onAbort: (callback: () => void) => () => void;
  controller: AbortController;
}>;

The abortion property provides:

  • abort(): Abort the Futur operation
  • isAborted(): Check if the Futur has been aborted
  • onAbort(callback): Register a callback for abort events (returns a function to remove the callback)
  • controller: The AbortController instance for use with fetch, streams, or other APIs that support AbortSignal

FuturRunner

The runner function type:

type FuturRunner = (payload: FuturPayload) => void;

Best Practices

  1. Use Futur.of for custom async logic: When you need full control over resolve/reject timing

  2. Use Futur.ofPromise for wrapping existing Promises: Cleaner syntax for Promise-based APIs

  3. Always handle both success and error cases: Use Result.isOk and Result.isErr for type-safe handling

  4. Leverage cancellation: Use abortion.abort() from within the runner, or futur.abort() from outside. Use abortion.controller.signal with fetch, streams, etc.

  5. Type your errors: Use the error type parameter to ensure type-safe error handling

  6. Remember Futurs are lazy: The operation won't start until you await the Futur

  7. Handle AbortedFailure: When using cancellation, check for AbortedFailure to handle cancelled operations gracefully

  8. Use the abort controller: Pass abortion.controller.signal to fetch, streams, or other APIs that support AbortSignal for automatic cancellation