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

@halo-lab/future

v0.0.6

Published

A better Promise

Downloads

7

Readme

Future

It is a Promise compatible type that allows to define and track error types.

Why is the default Promise definition type bad?

An asynchronous code may throw errors but the standard type of the Promise cannot tell you which errors you can handle in the catch method. Of course, you can define the error type explicitly, but you should know what en error can be at the time. It may be a hard task, especially if you are chaining a lot of promises and each of them may throw an error.

Installation

npm i @halo-lab/future

API

  1. Overview
  2. Types
  3. of/Future.of
  4. fail/Future.fail
  5. make/Future.make
  6. spawn/Future.spawn
  7. isThenable/Future.is
  8. merge/Future.merge
  9. settle/Future.settle
  10. first/Future.first
  11. oneOf/Future.oneOf
  12. map/Future.map
  13. mapErr/Future.mapErr
  14. recover/Future.recover
  15. after/Future.after

Usage

This package defines Future/FutureLike types which you can use instead of the Promise/PromiseLike. These types are interchangeable.

import { Future } from "@halo-lab/future";

const future: Future<number, Error> = new Promise((resolve, reject) => {
  const randomNumber = Math.random();

  if (randomNumber > 0.5) resolve(randomNumber);
  else reject(new Error("Random number is less than 0.5"));
});

const promise: Promise<number> = future;

By using the Future you can describe what errors a promise can be rejected with and TypeScript will help you remember and exhaustively handle them later.

// using the example above
const newFuture: Future<string[], never> = future.then(
  (number) => {
    /* do something useful */
    return ["foo"]; /* some result */
  },
  (error /* Error */) => {
    /* report that there is a problem and fix it */
    return [];
  }
);

Unfortunately, awaited future inside the try/catch block cannot populate an error type to the catch block, because TypeScript doesn't allow it (even explicitly). Though you can refer to the future type inside the try block and easily get what errors are expected to be thrown.

try {
  const value: string[] = await newFuture;
} catch (error) {
  /* error is not typed as never but any or unknown depending on your tsconfig */
}

This package defines and exports some functions that make Future creation and managing easier because default Promise typings are plain and don't pay any attention to the error types. These functions are exported separately and in a namespace (as a default export) for convenience.

Types

The Future namespace defines also aliases for the Future type: Self and for the FutureLike type: Like.

import Future from "@halo-lab/future";

function one(): Future.Self<1, never> {
  return Future.of(1);
}

const numberOne: Future.Like<1, never> = one();

Besides these types the library exports:

  1. NonThenable/Future.Not - a type that extracts thenable types from the type argument.
type A = Future.Not<number>; // -> number
type B = Future.Not<PromiseLike<string>>; // -> never
  1. AwaitedError/Future.Left - extracts an error type from the FutureLike. If a type parameter isn't thenable, it returns never.
type A = Future.Left<number>; // -> never
type B = Future.Left<PromiseLike<string>>; // -> unknown
type C = Future.Left<Future.Like<string, number>>; // -> number
  1. Future.Right - an alias to the native Awaited type.
type A = Future.Right<number>; // -> number
type B = Future.Right<PromiseLike<string>>; // -> string
type C = Future.Right<Future.Like<string, number>>; // -> string

of/Future.of

Wraps a value with a Future and immediately resolves it. If the value is another Future, the latter isn't wrapped.

const wrappedNumber: Future.Self<10, never> = Future.of(10);

const duplicatedWrappedNumber: Future.Self<10, never> =
  Future.of(wrappedNumber);

// The Future created from the Promise always has an `unknown`
// error type because it is really unknown unless the user knows it
// and provides the type manually.
const fromPromise: Future.Self<string, unknown> = Future.of(
  Promise.resolve("foo")
);

// If the value is rejected Future or Promise, the resulting Future
// also has the rejected state.
const failedFuture: Future<never, string> = Future.of(
  Promise.reject("A very helpful message")
);

fail/Future.fail

Wraps a value with a Future and immediately rejects it. If the value is another Future, it will be awaited and a new Future will be rejected with either value.

const failedFuture: Future.Self<never, "error"> = Future.fail("error");

const failedPromise: Future.Self<never, number> = Future.fail(
  Promise.resolve(7)
);

make/Future.make

Creates a Future with an executor callback. The same as the Promise constructor.

const future: Future<number, string> = Future.make((ok, err) => {
  doAsyncJob((error, result) => (error ? err(error) : ok(result)));
});

spawn/Future.spawn

Creates a Future from a callback's result. If the callback throws an error, the Future will be rejected. If the callback returns another Future it will be returned as is.

function calculateFibonacciNumber(position: number): number {
  // ...
}

const future: Future.Self<number, never> = Future.spawn(() => {
  return calculateFibonacciNumber(57);
});

// There is no way to mark a function in TypeScript that can
// throw an error, so you have to describe the error type that
// manually. Otherwise, it will be `never`.
const trickyFuture: Future.Self<never, Error> = Future.spawn(() => {
  throw new Error("an error is thrown");
});

You can pass arguments into the callback by providing them after it.

const future: Future.Self<number, never> = Future.spawn(
  (first, second) => {
    return first + second;
  },
  [34, 97]
);

isThenable/Future.is

Checks if a value is a thenable object.

Future.is(Future.of(1)); // -> true
Future.is(Promise.resolve("foo")); // -> true
Future.is({
  then(fulfill) {
    return Future.of(fulfill(Math.random()));
  },
}); // -> true
Future.is(3); // -> false

merge/Future.merge

Combines multiple Futures together waiting for all to complete or first to reject. Behaves as the Promise.all. Accepts a variable number of arguments or a single argument that should be Iterable or ArrayLike.

const result: Future.Self<readonly [number, string], boolean | string> =
  Future.merge(
    Future.spawn<number, boolean>(() => mayThrowABoolean()),
    Future.spawn<string, string>(() => mayThrowAString())
  );

const combined: Future.Self<readonly [1, 2], never> = Future.merge([
  Future.of(1),
  Future.of(2),
]);

settle/Future.settle

Combines multiple Futures together waiting for all to complete. Behaves as the Promise.allSettled. Accepts a variable number of arguments or a single argument that should be Iterable or ArrayLike. Promise's values are wrapped with the special Result object. It is a plain object with either ok property or err.

const future: Future.Self<
  readonly [Result<1, never>, Result<never, "bar">],
  never
> = Future.settle(Future.ok(1), Future.fail("bar"));

first/Future.first

Waits for the first Future to fulfill either successfuly or as a failure. Behaves as the Promise.race. Accepts a variable number of arguments or a single argument that should be Iterable or ArrayLike.

const future: Future.Self<1 | "foo", string | boolean> = Future.first(
  Future.make<1, string>(
    (ok, err) =>
      setTimeout(() => {
        Math.random() > 0.5
          ? ok(1)
          : err("numbers greater than 0.5 are not acceptable");
      }),
    100
  ),
  Future.spawn<"foo", boolean>(() => {
    if (Math.random() > 0.5) return "foo";
    else throw true;
  })
);

oneOf/Future.oneOf

Waits for the first Future to fulfill or all Futures to reject (array of errors is returned). Behaves as the Promise.any. Accepts a variable number of arguments or a single argument that should be Iterable or ArrayLike.

const future: Future.Self<1 | "foo", readonly [string, boolean]> = Future.oneOf(
  Future.make<1, string>(
    (ok, err) =>
      setTimeout(() => {
        Math.random() > 0.5
          ? ok(1)
          : err("numbers greater than 0.5 are not acceptable");
      }),
    100
  ),
  Future.spawn<"foo", boolean>(() => {
    if (Math.random() > 0.5) return "foo";
    else throw true;
  })
);

map/Future.map

Transforms a resolved value of the Future and returns another Future. It's a functional way to call onfulfilled callback of then method. The function has curried and uncurried forms.

const future: Future.Self<1, never> = Future.of(1);

const anotherFuture: Future.Self<number, never> = Future.map(
  future,
  (num) => num + 1
);

const multiplyByTen: <A>(
  future: Future.Like<number, A>
) => Future.Self<number, A> = Future.map((num) => num * 10);

const multipliedFuture: Future.Self<number, never> = multiplyByTen(future);

Callback is called only if the future is resolved. Otherwise it is returned as is.

mapErr/Future.mapErr

Transforms a rejected value of the Future into another rejected value and returns a rejected Future. The function has curried and uncurried forms.

const future: Future.Self<never, 1> = Future.fail(1);

const anotherFuture: Future.Self<never, number> = Future.mapErr(
  future,
  (num) => num + 1
);

const multiplyByTen: <A>(
  future: Future.Like<A, number>
) => Future.Self<A, number> = Future.mapErr((num) => num * 10);

const multipliedFuture: Future.Self<never, number> = multiplyByTen(future);

Callback is called only if the future is rejected. Otherwise it is returned as is.

recover/Future.recover

Transforms a rejected value of the Future into a resolved value and returns another Future. It's a functional way to call onrejected callback of the then method or the catch method. The function has curried and uncurried forms.

const future: Future.Self<OkResponse, ErrResponse> = fetch(
  "/api/v3/endpoint"
).then((response) =>
  response.ok ? response.json() : Future.fail(response.json())
);

// 1.
const futureWithDefaultResponse: Future.Self<OkResponse, never> =
  Future.recover(future, (errResponse) =>
    createDefaultResponseFrom(errResponse)
  );
// 2.
const repairResponse: (
  future: Future.Like<OkResponse, ErrResponse>
) => Future.Self<OkResponse, never> = Future.recover((errResponse) =>
  createDefaultResponseFrom(errResponse)
);

const repairedResponse: Future.Self<OkResponse, never> = repairResponse(future);

Callback is called only if the future is rejected. Otherwise it is returned as is.

after/Future.after

Registers a callback to be called after the Future fulfills either way. It's a functional way to call the finally method. The function has curried and uncurried forms.

const future: Future.Self<OkResponse, ErrResponse> = fetch(
  "/api/v3/endpoint"
).then((response) =>
  response.ok ? response.json() : Future.fail(response.json())
);

// 1.
const sameFuture: Future.Self<OkResponse, ErrResponse> = Future.after(
  future,
  () => doSomeSideEffect()
);
// 2.
const cleanupAfterJob: <OkResponse, ErrResponse>(
  future: Future.Like<OkResponse, ErrResponse>
) => Future.Self<OkResponse, ErrResponse> = Future.after(() => doSomeCleanup());

const sameFutureAfterCleanup: Future.Self<OkResponse, ErrResponse> =
  cleanupAfterJob(future);

If a callback throws an error or returns a rejected Future the error is propagated into the resulting Future.

const future: Future.Self<never, string | boolean> = Future.after(
  Future.fail("foo"),
  () => Future.fail(false)
);

Word from author

Have fun ✌️