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

bask-promise

v1.1.2

Published

Set of common promise utilities, not available in basic node.js features * Delay promise * Delay before or after promise * Wait until a minimum time has elapsed * Delay a rejection * Run promises in sequence, e.g. requests to database * Repeated fai

Readme

BASK Promise

Set of common promise utilities, not available in basic node.js features

  • Delay promise
  • Delay before or after promise
  • Wait until a minimum time has elapsed
  • Delay a rejection
  • Run promises in sequence, e.g. requests to database
  • Repeated failed promises
  • Repeated failed promises with exponential backoff
  • Timeout a promise
  • Race a promise against a guard promise
  • Deferred promise with state inspection

Install

npm i bask-promise --save

Then import module in code:

import * as promises from 'bask-promise';
promises.delay(100);

Or

import { delay } from 'bask-promise';
delay(100);

Delay

Simple delay

import { delay } from 'bask-promise';
delay(1000).then(() => console.log('Run after 1 second'));

Delay after promise

import { delayAfter } from 'bask-promise';
delayAfter(Promise.resolve('1 second delay after'), 1000);

Delay before promise

import { delayBefore } from 'bask-promise';
delayBefore(() => Promise.resolve('1 second delay before'), 1000);

Random delay

Random time period delay

import { random } from 'bask-promise';
random(1000).then(() => console.log('Run after 0...1 second'));

Specify lower bound

import { random } from 'bask-promise';
random(2000, 1000).then(() => console.log('Run after 1...2 seconds'));

Random delay after promise

import { randomAfter } from 'bask-promise';
randomAfter(Promise.resolve('0...1 second delay after'), 1000);
randomAfter(Promise.resolve('0...2 second delay after'), 2000, 1000);

Random delay before promise

import { randomBefore } from 'bask-promise';
randomBefore(() => Promise.resolve('0...1 second delay before'), 1000);
randomBefore(() => Promise.resolve('1...2 second delay before'), 2000, 1000);

Delay callback

If you want to use delay as a callback inside then, you can use delayFun(ms)

import { delayFun } from 'bask-promise';
Promise.resolve("Delay for 1s in then after promise").then(delayFun(1000));

Delay till

Waits until both the promise resolves and the minimum time has elapsed. Useful for avoiding UI flicker on fast operations — e.g. keeping a spinner visible for at least 500ms.

import { delayTill } from 'bask-promise';

// Resolves after at least 5000ms, even if fetchData() finishes in 1000ms
delayTill(fetchData(), 5000);

If the promise takes longer than milliseconds, it resolves as soon as the promise does:

// fetchData() takes 6000ms, milliseconds is 5000ms → resolves after 6000ms
delayTill(fetchData(), 5000);

By default, errors are propagated immediately. Set delayError: true to also delay rejection:

// If fetchData() fails early, the rejection is still held until 5000ms have passed
delayTill(fetchData(), 5000, true);

Delay throw

Returns a .catch-compatible callback that delays a rejection by the given number of milliseconds before re-throwing. Useful as a building block or as an inline callback.

import { delayThrow } from 'bask-promise';

// Delays the rejection by 1 second before propagating it
fetchData().catch(delayThrow(1000));

Combined with delayTill:

import { delayTill, delayThrow } from 'bask-promise';

// Equivalent to delayTill(fetchData(), 5000, true)
delayTill(fetchData().catch(delayThrow(5000)), 5000);

Sequence

Node.js have basic Promise.all function to run promises in parallel, but to run sequence you have to implement it yourself. This is a typical task for example when quering database or external services.

import { sequence } from 'bask-promise';
sequence([
        () => Promise.resolve('Query database 1'),
        () => Promise.resolve('Query database 2'),
    ])

If you have the same query function, but different arguments - use keySequence:

import { keySequence } from 'bask-promise';
keySequence([1,2,3,4,5], key => queryDatabasePromise(key));

If you want to mix sequence and parallel mode and define concurrency number - use concurrent. For concurrent <= 1 - this is sequence() For concurrent >= array length - this is Promise.all()

import { concurrent } from 'bask-promise';
concurrent(2, [
        () => Promise.resolve('Query database 1 (first thread)'),
        () => Promise.resolve('Query database 2 (second thread)'),
        () => Promise.resolve('Query database 3 (first thread)'),
        () => Promise.resolve('Query database 4 (second thread)')
    ])

Repeat

Sometimes you need to ensure request that is properly executed. But due to network connections, requests can fail. In that case you may want to repeat failed requests automatically.

Repeat up to 3 times (4 calls total):

import { repeat } from 'bask-promise';
repeat(() => repeatThisPromiseIfFails(), 3);

Repeat until success:

repeat(() => repeatThisPromiseIfFails(), -1);

Log every error

repeat(() => repeatThisPromiseIfFails(), 3, error => console.error(error));

Delay after error

import { repeat, delay } from 'bask-promise';
repeat(() => repeatThisPromiseIfFails(), 3, error => delay(1000));

Options object variant

All parameters can be passed as a single RepeatOptions object instead. This is the recommended style when specifying multiple options together, as it is more readable and self-documenting.

interface RepeatOptions<T> {
    promiseFun: () => Promise<T>;
    times?: number;        // default: 1
    onError?: (error: Error) => void;
    shouldRetry?: (error: Error, attempt: number) => boolean;
    backoff?: (attempt: number) => number;
    jitter?: 'full' | 'equal' | false;  // default: false
    signal?: AbortSignal;
}

Repeat up to 3 times:

import { repeat } from 'bask-promise';
repeat({ promiseFun: () => repeatThisPromiseIfFails(), times: 3 });

Repeat until success:

repeat({ promiseFun: () => repeatThisPromiseIfFails(), times: -1 });

Log every error and delay after each failure:

import { repeat, delay } from 'bask-promise';
repeat({
    promiseFun: () => repeatThisPromiseIfFails(),
    times: 3,
    onError: error => { console.error(error); return delay(1000); },
});

Retry only on specific errors:

import { repeat } from 'bask-promise';

// Only retry on network errors, fail immediately on anything else
repeat({
    promiseFun: () => fetchData(),
    times: 3,
    shouldRetry: error => error instanceof NetworkError,
});

Retry based on error code:

repeat({
    promiseFun: () => fetchData(),
    times: 5,
    shouldRetry: err => err.code === 'ECONNRESET',
});

Use the attempt argument to limit retries by error type and count together:

repeat({
    promiseFun: () => fetchData(),
    times: 10,
    shouldRetry: (error, attempt) => error instanceof NetworkError && attempt < 3,
});

Use backoff to add a delay before each retry. The function receives the current attempt number (starting at 0) and returns a delay in milliseconds:

Fixed delay between retries:

repeat({
    promiseFun: () => fetchData(),
    times: 5,
    backoff: () => 1000,
});

Exponential backoff (doubles each attempt, capped at 30 seconds):

import { repeat, exponential } from 'bask-promise';

repeat({
    promiseFun: () => fetchData(),
    times: 10,
    backoff: exponential(),                  // 1000ms, 2000ms, 4000ms … capped at 30000ms
});

exponential(baseDelay?, maxDelay?) is a convenience factory for the common pattern attempt => Math.min(baseDelay * 2 ** attempt, maxDelay). Both parameters are optional:

| Parameter | Default | Description | |---|---|---| | baseDelay | 1000 | Delay for the first retry in milliseconds | | maxDelay | 30000 | Upper cap in milliseconds |

exponential()           // 1000ms → 2000ms → 4000ms … → 30000ms
exponential(500)        // 500ms  → 1000ms → 2000ms … → 30000ms
exponential(200, 5000)  // 200ms  → 400ms  → 800ms  … → 5000ms

Combine backoff with shouldRetry and onError:

import { repeat, exponential } from 'bask-promise';

repeat({
    promiseFun: () => fetchData(),
    times: 5,
    shouldRetry: error => error instanceof NetworkError,
    backoff: exponential(1000, 30_000),
    onError: error => console.error(`Attempt failed: ${error.message}`),
});

Use jitter to add randomness to backoff delays, which prevents many clients from retrying in sync and hammering the server at the same time (AWS Architecture Blog).

| Value | Behaviour | |---|---| | false | No jitter — exact backoff value is used (default) | | 'full' | Uniformly random between 0 and the full backoff: random(0, backoff) | | 'equal' | Keeps half the backoff, randomises the other half: backoff/2 + random(0, backoff/2) |

'full' jitter — most effective at spreading load:

import { repeat, exponential } from 'bask-promise';

repeat({
    promiseFun: () => fetchData(),
    times: 10,
    backoff: exponential(),
    jitter: 'full',
});

'equal' jitter — steadier slowdown with some spread:

repeat({
    promiseFun: () => fetchData(),
    times: 10,
    backoff: exponential(),
    jitter: 'equal',
});

jitter without backoff has no effect, since there is no delay to randomise.

Pass an AbortSignal via signal to cancel the retry loop at any point. The returned promise rejects with the abort reason as soon as the signal fires — either immediately if the signal is already aborted, or mid-backoff-delay if it fires while waiting between retries.

Cancel after a timeout using AbortSignal.timeout:

import { repeat, exponential } from 'bask-promise';

repeat({
    promiseFun: () => fetchData(),
    times: -1,
    backoff: exponential(),
    signal: AbortSignal.timeout(10_000), // give up entirely after 10 seconds
});

Cancel manually with AbortController:

import { repeat } from 'bask-promise';

const ac = new AbortController();

repeat({
    promiseFun: () => fetchData(),
    times: -1,
    backoff: () => 1000,
    signal: ac.signal,
});

// cancel from outside whenever needed
ac.abort(new Error('user cancelled'));

Repeat with exponential backoff

Like repeat, but automatically adds an increasing delay between retries. The delay doubles after each failed attempt: baseDelay * 2^attempt.

Repeat up to 3 times with default 100ms base delay (delays: 100ms, 200ms, 400ms):

import { repeatExponential } from 'bask-promise';
repeatExponential({ promiseFun: () => repeatThisIfFails(), times: 3 });

Custom base delay:

repeatExponential({ promiseFun: () => repeatThisIfFails(), times: 5, baseDelay: 200 });

Repeat until success:

repeatExponential({ promiseFun: () => repeatThisIfFails(), times: -1 });

Log every error:

repeatExponential({
    promiseFun: () => repeatThisIfFails(),
    times: 3,
    onError: error => console.error(error),
});

Promise resolve from outside

If you need to create a promise instance, pass it somewhere, and resolve it later from outside, you can use deferred function

import { deferred } from 'bask-promise';
const d = deferred();

// use or pass promise somewhere
d.promise.then(() => null).catch(() => null);

// then resolve or reject from outside
d.resolve(42);
// or
d.reject(new Error('foo'));

Inspecting deferred state

The deferred object exposes read-only properties to inspect its current state at any time:

| Property | Type | Description | |---|---|---| | isPending | boolean | true until resolved or rejected | | isResolved | boolean | true after resolve() is called | | isFailed | boolean | true after reject() is called | | value | T \| undefined | The resolved value (available after the next microtask), undefined otherwise | | error | unknown \| undefined | The rejection reason, undefined otherwise |

import { deferred } from 'bask-promise';
const d = deferred();

d.isPending;   // true
d.isResolved;  // false
d.isFailed;    // false
d.value;       // undefined
d.error;       // undefined

d.resolve(42);

d.isPending;   // false
d.isResolved;  // true

await Promise.resolve(); // wait one microtask tick
d.value;       // 42
const d = deferred();
d.reject(new Error('boom'));

d.isPending;   // false
d.isFailed;    // true
d.error;       // Error: boom

Timer

Creates a promise that rejects after a given delay. Useful as a standalone building block or combined with left.

import { timer } from 'bask-promise';

// Rejects with default message after 1 second
timer(1000);

// Rejects with a custom message
timer(1000, 'Operation took too long');

Timeout

Races a promise against a timer. Rejects with a timeout error if the promise does not settle within the given milliseconds.

import { timeout } from 'bask-promise';

// Rejects with "Timeout exceeded: 5000ms" if fetchData() takes longer than 5 seconds
timeout(fetchData(), 5000);

Left

Races a promise against a guard promise. Resolves with the value of the primary promise, but rejects if the guard promise rejects — regardless of the primary promise state. The resolved value of the guard is ignored.

import { left } from 'bask-promise';

// Resolves with the result of primary(), but aborts if guard rejects
left(primary(), guard());

A practical use case is combining left with timer to build a custom timeout with a specific error message:

import { left, timer } from 'bask-promise';

left(fetchData(), timer(5000, 'fetchData timed out'));