Détail du package

extra-promise

BlackGlory9.4kMIT7.0.0

Utilities for JavaScript Promise and AsyncFunction

readme

extra-promise

Utilities for JavaScript Promise and async functions.

Install

npm install --save extra-promise
# or
yarn add extra-promise

API

interface INonBlockingChannel<T> {
  send(value: T): void
  receive(): AsyncIterable<T>
  close: () => void
}

interface IBlockingChannel<T> {
  send(value: T): Promise<void>
  receive(): AsyncIterable<T>
  close: () => void
}

interface IDeferred<T> {
  resolve(value: T): void
  reject(reason: unknown): void
}

functions

isPromise

function isPromise<T>(val: unknown): val is Promise<T>
function isntPromise<T>(val: T): val is Exclude<T, Promise<unknown>>

isPromiseLike

function isPromiseLike<T>(val: unknown): val is PromiseLike<T>
function isntPromiseLike<T>(val: T): val is Exclude<T, PromiseLike<unknown>>

delay

function delay(timeout: number, signal?: AbortSignal): Promise<void>

A simple wrapper for setTimeout.

timeout

function timeout(ms: number, signal?: AbortSignal): Promise<never>

It throws a TimeoutError after ms milliseconds.

try {
  result = await Promise.race([
    fetchData()
  , timeout(5000)
  ])
} catch (e) {
  if (e instanceof TimeoutError) ...
}

pad

function pad<T>(ms: number, fn: () => Awaitable<T>): Promise<T>

Run a function, but wait at least ms milliseconds before returning.

parallel

function parallel(
  tasks: Iterable<() => Awaitable<unknown>>
, concurrency: number = Infinity
): Promise<void>

Perform tasks in parallel.

The value range of concurrency is [1, Infinity]. Invalid values will throw Error.

parallelAsync

function parallelAsync(
  tasks: AsyncIterable<() => Awaitable<unknown>>
, concurrency: number // concurrency must be finite number
): Promise<void>

Same as parallel, but tasks is an AsyncIterable.

series

function series(
  tasks: Iterable<() => Awaitable<unknown>>
       | AsyncIterable<() => Awaitable<unknown>>
): Promise<void>

Perform tasks in order. Equivalent to parallel(tasks, 1).

waterfall

function waterfall<T>(
  tasks: Iterable<(result: unknown) => Awatiable<unknown>>
       | AsyncIterable<(result: unknown) => Awaitable<unknown>>
): Promise<T | undefined>

Perform tasks in order, the return value of the previous task will become the parameter of the next task. If tasks is empty, return Promise<undefined>.

each

function each(
  iterable: Iterable<T>
, fn: (element: T, i: number) => Awaitable<unknown>
, concurrency: number = Infinity
): Promise<void>

The async each operator for Iterable.

The value range of concurrency is [1, Infinity]. Invalid values will throw Error.

eachAsync

function eachAsync<T>(
  iterable: AsyncIterable<T>
, fn: (element: T, i: number) => Awaitable<unknown>
, concurrency: number // concurrency must be finite number
): Promise<void>

Same as each, but iterable is an AsyncIterable.

map

function map<T, U>(
  iterable: Iterable<T>
, fn: (element: T, i: number) => Awaitable<U>
, concurrency: number = Infinity
): Promise<U[]>

The async map operator for Iterable.

The value range of concurrency is [1, Infinity]. Invalid values will throw Error.

mapAsync

function mapAsync<T, U>(
  iterable: AsyncIterable<T>
, fn: (element: T, i: number) => Awaitable<U>
, concurrency: number // concurrency must be finite number
): Promise<U[]>

Same as map, but iterable is an AsyncIterable.

filter

function filter<T, U = T>(
  iterable: Iterable<T>
, fn: (element: T, i: number) => Awaitable<boolean>
, concurrency: number = Infinity
): Promise<U[]>

The async filter operator for Iterable.

The value range of concurrency is [1, Infinity]. Invalid values will throw Error.

filterAsync

function filterAsync<T, U = T>(
  iterable: AsyncIterable<T>
, fn: (element: T, i: number) => Awaitable<boolean>
, concurrency: number // concurrency must be finite number
): Promise<U[]>

Same as filter, but iterable is an AsyncIterable.

all

function all<T extends { [key: string]: PromiseLike<unknown> }>(
  obj: T
): Promise<{ [Key in keyof T]: UnpackedPromiseLike<T[Key]> }>

It is similar to Promise.all, but the first parameter is an object.

const { task1, task2 } = await all({
  task1: invokeTask1()
, task2: invokeTask2()
})

promisify

type Callback<T> = (err: any, result?: T) => void

function promisify<Result, Args extends any[] = unknown[]>(
  fn: (...args: [...args: Args, callback?: Callback<Result>]) => unknown
): (...args: Args) => Promise<Result>

The well-known promisify function.

callbackify

type Callback<T> = (err: any, result?: T) => void

function callbackify<Result, Args extends any[] = unknown[]>(
  fn: (...args: Args) => Awaitable<Result>
): (...args: [...args: Args, callback: Callback<Result>]) => void

The callbackify function, as opposed to promisify.

asyncify

function asyncify<Args extends any[], Result, This = unknown>(
  fn: (this: This, ...args: Args) => Awaitable<Result>
): (this: This, ...args: Promisify<Args>) => Promise<Result>

Turn sync functions into async functions.

const a = 1
const b = Promise.resolve(2)

const add = (a: number, b: number) => a + b

// BAD
add(a, await b) // 3

// GOOD
const addAsync = asyncify(add) // (a: number | PromiseLike<number>, b: number | PromiseLike<number>) => Promise<number>
await addAsync(a, b) // Promise<3>

It can also be used to eliminate the call stack:

// OLD
function count(n: number, i: number = 0): number {
  if (i < n) return count(n, i + 1)
  return i
}

count(10000) // RangeError: Maximum call stack size exceeded

// NEW
const countAsync = asyncify((n: number, i: number = 0): Awaitable<number> => {
  if (i < n) return countAsync(n, i + 1)
  return i
})

await countAsync(10000) // 10000

spawn

function spawn<T>(
  num: number
, create: (id: number) => Awaitable<T>
): Promise<T[]>

A sugar for create multiple values in parallel.

The parameter id is from 1 to num.

limitConcurrencyByQueue

function limitConcurrencyByQueue<T, Args extends any[]>(
  concurrency: number
, fn: (...args: Args) => PromiseLike<T>
): (...args: Args) => Promise<T>

Limit the number of concurrency, calls that exceed the number of concurrency will be delayed in order.

reusePendingPromises

type VerboseResult<T> = [value: T, isReuse: boolean]

interface IReusePendingPromisesOptions<Args> {
  createKey?: (args: Args) => unknown
  verbose?: true
}

function reusePendingPromises<T, Args extends any[]>(
  fn: (...args: Args) => PromiseLike<T>
, options: IReusePendingPromisesOptions<Args> & { verbose: true }
): (...args: Args) => Promise<VerboseResult<T>>
function reusePendingPromises<T, Args extends any[]>(
  fn: (...args: Args) => PromiseLike<T>
, options: IReusePendingPromisesOptions<Args> & { verbose: false }
): (...args: Args) => Promise<T>
function reusePendingPromises<T, Args extends any[]>(
  fn: (...args: Args) => PromiseLike<T>
, options: Omit<IReusePendingPromisesOptions<Args>, 'verbose'>
): (...args: Args) => Promise<T>
function reusePendingPromises<T, Args extends any[]>(
  fn: (...args: Args) => PromiseLike<T>
): (...args: Args) => Promise<T>

Returns a function that will return the same Promise for calls with the same parameters if the Promise is pending.

It generates cache keys based on the options.createKey function, The default value of options.createKey is a stable JSON.stringify implementation.

Classes

StatefulPromise

enum StatefulPromiseState {
  Pending = 'pending'
, Fulfilled = 'fulfilled'
, Rejected = 'rejected'
}

class StatefulPromise<T> extends Promise<T> {
  static from<T>(promise: PromiseLike<T>): StatefulPromise<T>

  get state(): StatefulPromiseState

  constructor(
    executor: (
      resolve: (value: T) => void
    , reject: (reason: any) => void
    ) => void
  )

  isPending(): boolean
  isFulfilled(): boolean
  isRejected(): boolean
}

A subclass of Promise used for testing, helps you understand the state of Promise.

Channel

class Channel<T> implements IBlockingChannel<T>

Implement MPMC(multi-producer, multi-consumer) FIFO queue communication with Promise and AsyncIterable.

  • send Send value to the channel, block until data is taken out by the consumer.
  • receive Receive value from the channel.
  • close Close the channel.

If the channel closed, send and receive will throw ChannelClosedError. AsyncIterator that have already been created do not throw ChannelClosedError, but return { done: true }.

const chan = new Channel<string>()
queueMicrotask(() => {
  await chan.send('hello')
  await chan.send('world')
})
for await (const value of chan.receive()) {
  console.log(value)
}

BufferedChannel

class BufferedChannel<T> implements IBlockingChannel<T> {
  constructor(bufferSize: number)
}

Implement MPMC(multi-producer, multi-consumer) FIFO queue communication with Promise and AsyncIterable. When the amount of data sent exceeds bufferSize, send will block until data in buffer is taken out by the consumer.

  • send Send value to the channel. If the buffer is full, block.
  • receive Receive value from the channel.
  • close Close the channel.

If the channel closed, send and receive will throw ChannelClosedError. AsyncIterator that have already been created do not throw ChannelClosedError, but return { done: true }.

const chan = new BufferedChannel<string>(1)

queueMicrotask(() => {
  await chan.send('hello')
  await chan.send('world')
})

for await (const value of chan.receive()) {
  console.log(value)
}

UnlimitedChannel

class UnlimitedChannel<T> implements INonBlockingChannel<T>

Implement MPMC(multi-producer, multi-consumer) FIFO queue communication with Promise and AsyncIterable.

UnlimitedChannel return a tuple includes three channel functions:

  • send Send value to the channel. There is no size limit on the buffer, all sending will return immediately.
  • receive Receive value from the channel.
  • close Close the channel.

If the channel closed, send and receive will throw ChannelClosedError. AsyncIterator that have already been created do not throw ChannelClosedError, but return { done: true }.

const chan = new UnlimitedChannel<string>()

queueMicrotask(() => {
  chan.send('hello')
  chan.send('world')
})

for await (const value of chan.receive()) {
  console.log(value)
}

Deferred

class Deferred<T> implements PromiseLike<T>, IDeferred<T>

Deferred is a Promise that separates resolve() and reject() from the constructor.

MutableDeferred

class MutableDeferred<T> implements PromiseLike<T>, IDefrred<T>

MutableDeferred is similar to Deferred, but its resolve() and reject() can be called multiple times to change the value.

const deferred = new MutableDeferred()
deferred.resolve(1)
deferred.resolve(2)

await deferred // resolved(2)

ReusableDeferred

class ReusableDeferred<T> implements PromiseLike<T>, IDeferred<T>

ReusableDeferred is similar to MutableDeferred, but its internal Deferred will be overwritten with a new pending Deferred after each call.

const deferred = new ReusableDeferred()
deferred.resolve(1)
queueMicrotask(() => deferred.resolve(2))

await deferred // pending, resolved(2)

DeferredGroup

class DeferredGroup<T> implements IDeferred<T> {
  add(deferred: IDeferred<T>): void
  remove(deferred: IDeferred<T>): void
  clear(): void
}

LazyPromise

class LazyPromise<T> implements PromiseLike<T> {
  then: PromiseLike<T>['then']

  constructor(
    executor: (resolve: (value: T) => void
  , reject: (reason: any) => void) => void
  )
}

LazyPromise constructor is the same as Promise.

The difference with Promise is that LazyPromise only performs executor after then method is called.

Semaphore

type Release = () => void

class Semaphore {
  constructor(count: number)

  acquire(): Promise<Release>
  acquire<T>(handler: () => Awaitable<T>): Promise<T>
}

Mutex

type Release = () => void

class Mutex extends Semaphore {
  acquire(): Promise<Release>
  acquire<T>(handler: () => Awaitable<T>): Promise<T>
}

DebounceMicrotask

class DebounceMicrotask {
  queue(fn: () => void): void
  cancel(fn: () => void): boolean
}

queue can create a microtask, if the microtask is not executed, multiple calls will only queue it once.

cancel can cancel a microtask before it is executed.

DebounceMacrotask

class DebounceMacrotask {
  queue(fn: () => void): void
  cancel(fn: () => void): boolean
}

queue can create a macrotask, if the macrotask is not executed, multiple calls will only queue it once.

cancel can cancel a macrotask before it is executed.

TaskRunner

class TaskRunnerDestroyedError extends CustomError {}

class TaskRunner {
  constructor(
    concurrency: number = Infinity
  , rateLimit?: {
      duration: number
      limit: number
    }
  )

  /**
   * @throws {TaskRunnerDestroyedError}
   */
  run(task: (signal: AbortSignal) => Awaitable<T>, signal?: AbortSignal): Promise<T>

  destroy(): void
}

A task runner, it will execute tasks in FIFO order.

changelog

Changelog

All notable changes to this project will be documented in this file. See standard-version for commit guidelines.

7.0.0 (2024-10-25)

⚠ BREAKING CHANGES

  • Node.js v16 => Node.js v18.17.0

Features

  • add an optional signal parameter to delay, timeout (c0e9147)

6.2.0 (2024-01-05)

Features

  • TaskRunner: add AbortSignal support (2263d3a)
  • TaskRunner: add rate limiting support (40e229a)

6.1.0 (2023-12-29)

Features

  • reusePendingPromises: add options.createKey support (336331a)

6.0.8 (2023-08-02)

6.0.7 (2023-06-10)

Bug Fixes

6.0.6 (2023-04-05)

6.0.5 (2023-03-18)

6.0.4 (2023-03-18)

6.0.3 (2023-01-27)

6.0.2 (2023-01-22)

6.0.1 (2023-01-22)

6.0.0 (2023-01-21)

⚠ BREAKING CHANGES

  • CommonJS => ESM

  • commonjs => esm (d10b27e)

5.0.1 (2023-01-21)

5.0.0 (2022-12-26)

⚠ BREAKING CHANGES

  • The minimal version of Node.js is 16.

  • upgrade dependencies (d249aee)

4.4.0 (2022-11-27)

Features

4.3.1 (2022-11-24)

4.3.0 (2022-11-11)

Features

  • specify behaviors of channels after they are closed (7ca4bbb)

4.2.0 (2022-11-10)

Features

Bug Fixes

  • asyncify: the function signature (9837302)

4.1.0 (2022-11-07)

Features

  • add isPromise, isPromiseLike (8ab8c7a)

4.0.0 (2022-10-20)

⚠ BREAKING CHANGES

  • Changed behaviors of Channel, BufferChannel, UnlimitedChannel.
  • Removed Signal, SignalGroup
  • renamed reusePendingPromise to reusePendingPromises
  • Renamed queueConcurrency to limitConcurrencyByQueue
  • Rewrote TaskRunner
  • Removed throttleConcurrency because it has a terrible name.
  • ExtraPromise now is StatefulPromise.
  • toExtraPromise is now ExtraPromise.from.
  • Removed throttleUntilDone, because other functions can replace it

Features

  • merge toExtraPromise into ExtraPromise (b3f902f)
  • remove Signal, rewrite SignalGroup to DeferredGroup (e6368e0)
  • remove throttleConcurrency (a54fc56)
  • remove throttleUntilDone (8b21dff)
  • improve behaviors (5a41bf8)
  • rename queueConcurrency to limitConcurrencyByQueue (6ab8e18)
  • rename reusePendingPromise to reusePendingPromises (8b8378b)
  • rewrite ExtraPromise to StatefulPromise (3614a37)
  • rewrite TaskRunner (24cb19e)

3.2.2 (2022-09-13)

3.2.1 (2022-09-12)

3.2.0 (2022-08-29)

Features

  • ExtraPromise: add state getter (a18fb41)

3.1.1 (2022-08-27)

Bug Fixes

3.1.0 (2022-08-27)

Features

3.0.0 (2022-08-27)

⚠ BREAKING CHANGES

  • Renamed ReusableDeferred to MutableDeferred

Features

  • rename ReusableDeferred to MutableDeferred (6f1937e)

2.4.1 (2022-08-06)

Bug Fixes

  • function signautre overloads (c9a521b)

2.4.0 (2022-08-04)

Features

  • add options for reusePendingPromise (475fca5)

2.3.0 (2022-07-25)

Features

  • add reusePendingPromise (cdcc61f)

2.2.0 (2022-06-18)

Features

  • improve promisify for an edge case (03cbbb5)

Bug Fixes

  • callbackify for edge cases (d630910)
  • callbackify for edge cases (57c788b)

2.1.1 (2022-06-18)

Bug Fixes

  • an edge case for promisify (8765b17)

2.1.0 (2022-06-18)

Features

2.0.0 (2022-05-16)

⚠ BREAKING CHANGES

  • rewrite TaskRunner
  • cascadify removed

Features

1.0.2 (2022-03-23)

1.0.1 (2022-03-17)

1.0.0 (2022-03-05)

⚠ BREAKING CHANGES

  • remove isPromise, isntPromise, isPromiseLike, isntPromiseLike

Features

  • remove isPromise, isntPromise, isPromiseLike, isntPromiseLike (6335d1c)

0.21.2 (2022-01-24)

Features

0.21.1 (2022-01-06)

0.21.0 (2021-12-17)

⚠ BREAKING CHANGES

    • Remove timeoutSignal, withAbortSignal, raceAbortSignals

Features

  • remove timeoutSignal, withAbortSignal, raceAbortSignals (d810f3c)

0.20.0 (2021-12-16)

⚠ BREAKING CHANGES

    • The minimum version is Node.js v16
  • upgrade Node.js version (57975a3)

0.19.7 (2021-12-16)

0.19.6 (2021-12-13)

0.19.5 (2021-12-13)

0.19.4 (2021-12-04)

0.19.3 (2021-12-04)

Features

0.19.2 (2021-12-01)

0.19.1 (2021-10-14)

0.19.0 (2021-10-12)

⚠ BREAKING CHANGES

  • methods Semaphore.acquire and Mutx.acquire will unlock after throwing

Features

  • methods Semaphore.acquire and Mutx.acquire will unlock after throwing (7b076f9)

0.18.7 (2021-09-25)

0.18.6 (2021-09-19)

Features

  • add queueConcurrency, throttleConcurrency, throttleUntilDone (8726c3d)

0.18.5 (2021-09-16)

0.18.4 (2021-09-14)

0.18.3 (2021-09-14)

Features

0.18.2 (2021-08-02)

0.18.1 (2021-07-14)

Features

  • improve Semaphore, Mutex (950ab8b)

0.18.0 (2021-07-14)

⚠ BREAKING CHANGES

  • The acquire method of Semaphore and Mutex now always return Promise.

Features

  • improve Semaphore, Mutex (41af3a9)

0.17.6 (2021-07-13)

Bug Fixes

  • close unexhausted iterators (2545371)

0.17.5 (2021-07-12)

0.17.4 (2021-07-03)

0.17.3 (2021-05-30)

Bug Fixes

0.17.2 (2021-05-30)

Features

0.17.1 (2021-05-17)

0.17.0 (2021-05-17)

⚠ BREAKING CHANGES

    • remove InvalidArgumentError
  • remove InvalidArgumentsLengthError

Features

0.16.6 (2021-05-16)

0.16.5 (2021-05-16)

0.16.4 (2021-04-30)

Features

0.16.3 (2021-04-30)

Features

0.16.2 (2021-03-27)

Features

0.16.1 (2021-03-27)

Features

Bug Fixes

0.16.0 (2021-03-22)

⚠ BREAKING CHANGES

  • move retryUntil to extra-retry

Features

  • move retryUntil to extra-retry (e38211d)

0.15.6 (2021-03-17)

0.15.5 (2021-03-15)

Features

0.15.4 (2021-03-05)

0.15.3 (2021-03-05)

Features

  • add isntPromise, isntPromiseLike (0b31adf)

0.15.2 (2021-03-05)

0.15.1 (2021-03-05)

Bug Fixes

0.15.0 (2021-03-05)

⚠ BREAKING CHANGES

  • remove retryForever, retryCount

Features

  • remove retryForever, retryCount (fcb5d19)

0.14.5 (2021-03-02)

Features

  • add parameter fatalErrors (02c39f6)

0.14.4 (2021-02-25)

0.14.3 (2021-02-04)

0.14.2 (2021-02-03)

Bug Fixes

0.14.1 (2021-02-03)

0.14.0 (2021-02-02)

⚠ BREAKING CHANGES

  • cascadable => Cascadable

Features

  • rename cascadable to Cascadable (a14c6e0)

0.13.15 (2021-01-20)

Bug Fixes

0.13.14 (2021-01-20)

0.13.13 (2021-01-15)

Bug Fixes

0.13.12 (2021-01-05)

Features

0.13.11 (2021-01-04)

0.13.10 (2021-01-04)

0.13.9 (2021-01-04)

Features

0.13.8 (2021-01-01)

Features

  • add retryForever, retryCount (22c97dd)

0.13.7 (2020-12-23)

0.13.6 (2020-12-21)

Bug Fixes

0.13.5 (2020-12-21)

Features

0.13.4 (2020-12-20)

0.13.3 (2020-12-20)

Bug Fixes

0.13.2 (2020-12-19)

0.13.1 (2020-12-19)

0.13.0 (2020-12-19)

⚠ BREAKING CHANGES

  • makeChannel => Channel makeBufferedChannel => BufferedChannel makeUnlimitedChannel => UnlimitedChannel

Features

  • replace makeChannel functions with classes (933bc6c)

0.12.1 (2020-12-19)

0.12.0 (2020-12-19)

⚠ BREAKING CHANGES

  • remove Signal#refresh

Features

0.11.0 (2020-12-19)

⚠ BREAKING CHANGES

  • InvalidArgumentError extends CustomError InvalidArugmentsLengthError extends CustomError

Features

0.10.6 (2020-12-19)

Features

0.10.5 (2020-12-18)

Features

0.10.4 (2020-12-18)

Features

0.10.3 (2020-12-18)

Bug Fixes

0.10.2 (2020-12-18)

Features

0.10.1 (2020-12-18)

Features

  • experimental support MPMC (91d0749)

0.10.0 (2020-12-18)

⚠ BREAKING CHANGES

  • remove makeChannel

Features

0.9.6 (2020-12-17)

Features

0.9.5 (2020-11-21)

0.9.4 (2020-11-21)

Features

0.9.3 (2020-11-08)

Features

0.9.2 (2020-11-08)

0.9.1 (2020-10-10)

0.9.0 (2020-10-05)

⚠ BREAKING CHANGES

  • makeChannel was renamed to makeUnlimitedChannel makeBlockingChannel was renamed to makeBufferedChannel

  • refactor all channel functions (149d2b3)