…
2026-10-07 12:01:13 +00:00
2026-10-07 12:01:13 +00:00
2026-10-07 12:01:13 +00:00
…

@push.rocks/smartpromise

A library for simple promises and Deferred constructs with TypeScript support.

Issue Reporting and Security

For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.

Install

npm install @push.rocks/smartpromise --save

This module is designed to be used with TypeScript for the best developer experience, providing type safety and IntelliSense in your IDE.

Usage

@push.rocks/smartpromise simplifies the use of promises and deferred constructs in TypeScript, offering a set of utility functions that extend native Promise capabilities in specific scenarios. This guide walks you through its functionalities, showcasing how to leverage this library in a TypeScript project.

Setting Up Your Project

Ensure your TypeScript project is configured to support ES Module syntax. In your tsconfig.json, you should have:

{
  "compilerOptions": {
    "module": "ESNext",
    "target": "ESNext",
    "moduleResolution": "node"
  }
}

Basic Usage

Creating a Deferred

Deferred objects in @push.rocks/smartpromise give you more control over your promises, allowing for manual resolution or rejection beyond the initial executor function.

import { defer } from '@push.rocks/smartpromise';

async function exampleDeferred(): Promise<string> {
  const myDeferred = defer<string>();

  // Simulate an async task
  setTimeout(() => {
    myDeferred.resolve('Hello, Deferred!');
  }, 1000);

  return myDeferred.promise;
}

exampleDeferred().then((result) => console.log(result));

Cumulative Deferred

For scenarios where multiple asynchronous tasks need to be tracked collectively before proceeding, use CumulativeDeferred. It waits for all added promises to resolve.

import { cumulativeDefer } from '@push.rocks/smartpromise';

async function exampleCumulativeDeferred() {
  const myCumulativeDeferred = cumulativeDefer();

  for (let i = 0; i < 5; i++) {
    myCumulativeDeferred.addPromise(new Promise((resolve) => setTimeout(resolve, 1000 * i)));
  }

  await myCumulativeDeferred.promise;
  console.log('All tasks completed!');
}

exampleCumulativeDeferred();

Utilizing Resolved and Rejected Promises

Quickly create already resolved or rejected promises for testing or initialization purposes.

import { resolvedPromise, rejectedPromise } from '@push.rocks/smartpromise';

resolvedPromise('immediately resolved').then(console.log);
rejectedPromise('immediately rejected').catch(console.error);

Advanced Use Cases

Promisify Callback Functions

fromCallback turns a function that takes a Node.js-style callback into a promise: it resolves with the result and rejects with the error the callback was called with.

import * as fs from 'node:fs';
import { fromCallback, type TCallbackError } from '@push.rocks/smartpromise';

const content = await fromCallback<Buffer>((callback) => fs.readFile('./package.json', callback)).catch((error: TCallbackError) => {
  console.log(error.code); // 'ENOENT' when the file is missing
  throw error;
});

TCallbackError is an Error with the optional code, errno, syscall and path of Node.js's system errors. It names no Node.js type, so the package's declarations compile in projects without @types/node (a browser bundle, types: []).

Handling Timeouts and Continuations

Managing timeouts or long-running promises gets easier with helper functions.

import { timeoutWrap, timeoutAndContinue } from '@push.rocks/smartpromise';

async function exampleTimeout() {
  const myPromise = new Promise((resolve) => setTimeout(() => resolve('Done!'), 2000));

  // Will reject if the promise does not resolve within 1 second
  try {
    const result = await timeoutWrap(myPromise, 1000);
    console.log(result);
  } catch (error) {
    console.error('Promise timed out');
  }

  // Continues after 1 second, regardless of whether the promise has resolved
  const result = await timeoutAndContinue(myPromise, 1000);
  console.log(result); // May log `null` if the original promise did not resolve in time
}

exampleTimeout();

Delays

delayFor(ms, passOn?, unrefed = false) waits like setTimeout and resolves with the optional pass-on value. delayForRandom(minMs, maxMs, passOn?, unrefed = false) waits for a random duration inside the range, which is handy for jitter in retry loops.

import { delayFor, delayForRandom } from '@push.rocks/smartpromise';

await delayFor(1000);

const status = await delayFor(250, 'ready'); // 'ready'

await delayForRandom(500, 1500);

Pass true as the last argument to unref the timer: it then does not keep a Node.js process alive by itself. Runtimes whose timer handles have no unref() (browsers, Deno) ignore it.

Cancellable Timers

Timeout<T> is a promise-backed timer you can cancel, unref and inspect.

import { Timeout } from '@push.rocks/smartpromise';

const timeout = new Timeout<string>(60_000, 'done');

console.log(timeout.getTimeLeft()); // milliseconds left, e.g. 60000

timeout.makeUnrefed(); // do not keep a Node.js process alive for this timer
timeout.cancel(); // clears the timer; `timeout.promise` never settles
  • promise: Promise<T> resolves with the pass-on value once the delay has elapsed.
  • cancel() clears the timer. The promise stays pending, so only cancel a timer you no longer await. Cancelling a finished or already cancelled timer does nothing.
  • getTimeLeft() returns the whole milliseconds left. It is measured on a monotonic clock, so wall-clock adjustments do not skew it; it returns 0 once the promise has resolved and keeps the time that was left at cancellation after cancel().
  • makeUnrefed() unrefs the timer where the runtime supports it, including every later chunk of a long delay.

Delays longer than the platform timer ceiling of 2,147,483,647 ms (about 24.8 days) are waited in chunks, so they resolve on time instead of firing after 1 ms. delayFor and delayForRandom use Timeout, so they inherit this.

Map and Reduce Asynchronous Functions

Suppose you have a collection of items that you need to process asynchronously. You can use the map function to apply an asynchronous function to each item in the array and wait for all the promises to resolve.

import { map } from '@push.rocks/smartpromise';

async function processData(items: string[]): Promise<string[]> {
  return map(items, async (item) => {
    // Simulate an async operation
    await new Promise((resolve) => setTimeout(resolve, 100));
    return item.toUpperCase();
  });
}

processData(['hello', 'world']).then(console.log);

Handling Complex Promise Scenarios

Managing Multiple Deferreds

If you have multiple deferred objects and you want to manage them collectively, you can do so using the CumulativeDeferred class. This is especially useful when you have a dynamic number of deferreds to resolve.

import { defer, cumulativeDefer } from '@push.rocks/smartpromise';

async function exampleComplexDeferred(): Promise<void> {
  const task1 = defer<string>();
  const task2 = defer<string>();
  const cumulative = cumulativeDefer();

  cumulative.addPromise(task1.promise);
  cumulative.addPromise(task2.promise);

  // Simulate async tasks
  setTimeout(() => task1.resolve('Task 1 complete'), 1000);
  setTimeout(() => task2.resolve('Task 2 complete'), 2000);

  await cumulative.promise;

  console.log('All tasks completed');
}

exampleComplexDeferred();

Other Utilities

Race Condition Handling with getFirstTrueOrFalse

This helper function resolves immediately when one of the provided promises resolves to true or when all promises are resolved to false.

import { getFirstTrueOrFalse } from '@push.rocks/smartpromise';

async function exampleRaceCondition() {
  const task1 = new Promise<boolean>((resolve) => setTimeout(() => resolve(true), 1000));
  const task2 = new Promise<boolean>((resolve) => setTimeout(() => resolve(false), 2000));

  const result = await getFirstTrueOrFalse([task1, task2]);
  console.log(result); // Outputs: true
}

exampleRaceCondition();

Complete Use Case

Let's create a comprehensive example that showcases multiple features of @push.rocks/smartpromise.

import {
  defer,
  cumulativeDefer,
  resolvedPromise,
  rejectedPromise,
  timeoutWrap,
  timeoutAndContinue,
  map,
  getFirstTrueOrFalse,
} from '@push.rocks/smartpromise';

async function completeUseCaseExample() {
  console.log('Starting Complete Use Case Example!');

  // Using Deferred
  const myDeferred = defer<string>();
  setTimeout(() => myDeferred.resolve('Deferred Resolved!'), 500);
  console.log(await myDeferred.promise); // Outputs: "Deferred Resolved!"

  // Using Cumulative Deferred
  const myCumulativeDeferred = cumulativeDefer();
  myCumulativeDeferred.addPromise(new Promise((resolve) => setTimeout(() => resolve('Task 1'), 1000)));
  myCumulativeDeferred.addPromise(new Promise((resolve) => setTimeout(() => resolve('Task 2'), 2000)));
  await myCumulativeDeferred.promise;
  console.log('All cumulative tasks completed');

  // Using Resolved and Rejected Promises
  await resolvedPromise('Instant Resolve').then(console.log); // Outputs: "Instant Resolve"
  await rejectedPromise('Instant Reject').catch(console.error); // Outputs: "Instant Reject"

  // Using timeoutWrap
  try {
    const delayedPromise = new Promise((resolve) => setTimeout(() => resolve('Finished'), 3000));
    const result = await timeoutWrap(delayedPromise, 1000);
    console.log(result);
  } catch (e) {
    console.error('Timeout occurred'); // Outputs: "Timeout occurred"
  }

  // Using timeoutAndContinue
  const resultContinue = await timeoutAndContinue(
    new Promise((resolve) => setTimeout(() => resolve('Finished eventually'), 3000)),
    1000
  );
  console.log(resultContinue); // Outputs: null (since it didn't resolve in 1 second)

  // Using Map
  const items = ['a', 'b', 'c'];
  const processedItems = await map(items, async (item) => {
    await new Promise((resolve) => setTimeout(resolve, 500));
    return item.toUpperCase();
  });
  console.log(processedItems); // Outputs: ['A', 'B', 'C']

  // Using getFirstTrueOrFalse
  const raceResults = await getFirstTrueOrFalse([
    new Promise<boolean>((resolve) => setTimeout(() => resolve(false), 1000)),
    new Promise<boolean>((resolve) => setTimeout(() => resolve(true), 2000)),
  ]);
  console.log(raceResults); // Outputs: true

  console.log('Complete Use Case Example Finished!');
}

completeUseCaseExample();

Testing Your Code

Testing is crucial to ensure the reliability of your asynchronous workflows. You can write tests using the @push.rocks/tapbundle library to create unit tests for your promises and deferred constructs.

import { tap, expect } from '@push.rocks/tapbundle';
import * as smartpromise from '@push.rocks/smartpromise';

tap.test('should resolve a deferred promise', async () => {
  const deferred = smartpromise.defer<string>();
  deferred.resolve('Resolved!');
  const result = await deferred.promise;
  expect(result).toEqual('Resolved!');
});

tap.test('should timeout a long-running promise', async () => {
  const longRunningPromise = new Promise((resolve) => setTimeout(resolve, 2000));
  try {
    await smartpromise.timeoutWrap(longRunningPromise, 1000);
  } catch (err) {
    expect(err.message).toEqual('timeout');
  }
});

tap.test('should map async function to an array', async () => {
  const inputArray = ['a', 'b'];
  const result = await smartpromise.map(inputArray, async (item) => item.toUpperCase());
  expect(result).toEqual(['A', 'B']);
});

tap.start();

By following this guide and using the examples provided, you should be able to effectively use @push.rocks/smartpromise for managing promises and deferred constructs in your TypeScript project. The library's extensive utility functions, combined with TypeScript support, make it a powerful tool for modern asynchronous programming needs.

Explore the full range of features and feel free to read through the source code to learn more about the implementation details. Happy coding!

Migration from @push.rocks/smartdelay

@push.rocks/smartdelay is now part of @push.rocks/smartpromise. The signatures are unchanged, so only the import changes:

@push.rocks/smartdelay @push.rocks/smartpromise
smartdelay.delayFor(ms, passOn?, unrefed?) smartpromise.delayFor(ms, passOn?, unrefed?)
smartdelay.delayForRandom(minMs, maxMs, passOn?, unrefed?) smartpromise.delayForRandom(minMs, maxMs, passOn?, unrefed?)
new smartdelay.Timeout<T>(ms, passOn?, unrefed?) new smartpromise.Timeout<T>(ms, passOn?, unrefed?)
timeout.promise, timeout.cancel(), timeout.makeUnrefed(), timeout.getTimeLeft() unchanged
// before
import * as smartdelay from '@push.rocks/smartdelay';
await smartdelay.delayFor(100);

// after
import * as smartpromise from '@push.rocks/smartpromise';
await smartpromise.delayFor(100);

Then remove @push.rocks/smartdelay from your dependencies. getTimeLeft() now returns 0 after the promise resolved, keeps the value left at cancellation after cancel() instead of counting down, and ignores wall-clock adjustments.

This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the license file within this repository.

Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.

Trademarks

This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.

Company Information

Task Venture Capital GmbH
Registered at District court Bremen HRB 35230 HB, Germany

For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.

By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

S
Description
A library for simple promises and Deferred constructs with TypeScript support.
Readme
1.1 MiB
Languages
TypeScript 100%