https://github.com/blackglory/extra-promise
🌳 Utilities for JavaScript Promise and async functions.
https://github.com/blackglory/extra-promise
browser esm library nodejs npm-package typescript
Last synced: 10 months ago
JSON representation
🌳 Utilities for JavaScript Promise and async functions.
- Host: GitHub
- URL: https://github.com/blackglory/extra-promise
- Owner: BlackGlory
- License: mit
- Created: 2017-07-11T09:43:31.000Z (almost 9 years ago)
- Default Branch: master
- Last Pushed: 2025-01-15T06:38:03.000Z (over 1 year ago)
- Last Synced: 2025-08-25T03:54:20.952Z (10 months ago)
- Topics: browser, esm, library, nodejs, npm-package, typescript
- Language: TypeScript
- Homepage: https://www.npmjs.com/package/extra-promise
- Size: 1.74 MB
- Stars: 4
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# extra-promise
Utilities for JavaScript `Promise` and async functions.
## Install
```sh
npm install --save extra-promise
# or
yarn add extra-promise
```
## API
```ts
interface INonBlockingChannel {
send(value: T): void
receive(): AsyncIterable
close: () => void
}
interface IBlockingChannel {
send(value: T): Promise
receive(): AsyncIterable
close: () => void
}
interface IDeferred {
resolve(value: T): void
reject(reason: unknown): void
}
```
### functions
#### isPromise
```ts
function isPromise(val: unknown): val is Promise
function isntPromise(val: T): val is Exclude>
```
#### isPromiseLike
```ts
function isPromiseLike(val: unknown): val is PromiseLike
function isntPromiseLike(val: T): val is Exclude>
```
#### delay
```ts
function delay(timeout: number, signal?: AbortSignal): Promise
```
A simple wrapper for `setTimeout`.
#### timeout
```ts
function timeout(ms: number, signal?: AbortSignal): Promise
```
It throws a `TimeoutError` after `ms` milliseconds.
```ts
try {
result = await Promise.race([
fetchData()
, timeout(5000)
])
} catch (e) {
if (e instanceof TimeoutError) ...
}
```
#### pad
```ts
function pad(ms: number, fn: () => Awaitable): Promise
```
Run a function, but wait at least `ms` milliseconds before returning.
#### parallel
```ts
function parallel(
tasks: Iterable<() => Awaitable>
, concurrency: number = Infinity
): Promise
```
Perform tasks in parallel.
The value range of `concurrency` is [1, Infinity].
Invalid values will throw `Error`.
#### parallelAsync
```ts
function parallelAsync(
tasks: AsyncIterable<() => Awaitable>
, concurrency: number // concurrency must be finite number
): Promise
```
Same as `parallel`, but `tasks` is an `AsyncIterable`.
#### series
```ts
function series(
tasks: Iterable<() => Awaitable>
| AsyncIterable<() => Awaitable>
): Promise
```
Perform tasks in order.
Equivalent to `parallel(tasks, 1)`.
#### waterfall
```ts
function waterfall(
tasks: Iterable<(result: unknown) => Awatiable>
| AsyncIterable<(result: unknown) => Awaitable>
): Promise
```
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`.
#### each
```ts
function each(
iterable: Iterable
, fn: (element: T, i: number) => Awaitable
, concurrency: number = Infinity
): Promise
```
The async `each` operator for Iterable.
The value range of `concurrency` is [1, Infinity].
Invalid values will throw `Error`.
#### eachAsync
```ts
function eachAsync(
iterable: AsyncIterable
, fn: (element: T, i: number) => Awaitable
, concurrency: number // concurrency must be finite number
): Promise
```
Same as `each`, but `iterable` is an `AsyncIterable`.
#### map
```ts
function map(
iterable: Iterable
, fn: (element: T, i: number) => Awaitable
, concurrency: number = Infinity
): Promise
```
The async `map` operator for Iterable.
The value range of `concurrency` is [1, Infinity].
Invalid values will throw `Error`.
#### mapAsync
```ts
function mapAsync(
iterable: AsyncIterable
, fn: (element: T, i: number) => Awaitable
, concurrency: number // concurrency must be finite number
): Promise
```
Same as `map`, but `iterable` is an `AsyncIterable`.
#### filter
```ts
function filter(
iterable: Iterable
, fn: (element: T, i: number) => Awaitable
, concurrency: number = Infinity
): Promise
```
The async `filter` operator for Iterable.
The value range of `concurrency` is [1, Infinity].
Invalid values will throw `Error`.
#### filterAsync
```ts
function filterAsync(
iterable: AsyncIterable
, fn: (element: T, i: number) => Awaitable
, concurrency: number // concurrency must be finite number
): Promise
```
Same as `filter`, but `iterable` is an `AsyncIterable`.
#### all
```ts
function all }>(
obj: T
): Promise<{ [Key in keyof T]: UnpackedPromiseLike }>
```
It is similar to `Promise.all`, but the first parameter is an object.
```ts
const { task1, task2 } = await all({
task1: invokeTask1()
, task2: invokeTask2()
})
```
#### promisify
```ts
type Callback = (err: any, result?: T) => void
function promisify(
fn: (...args: [...args: Args, callback?: Callback]) => unknown
): (...args: Args) => Promise
```
The well-known `promisify` function.
#### callbackify
```ts
type Callback = (err: any, result?: T) => void
function callbackify(
fn: (...args: Args) => Awaitable
): (...args: [...args: Args, callback: Callback]) => void
```
The `callbackify` function, as opposed to `promisify`.
#### asyncify
```ts
function asyncify(
fn: (this: This, ...args: Args) => Awaitable
): (this: This, ...args: Promisify) => Promise
```
Turn sync functions into async functions.
```ts
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, b: number | PromiseLike) => Promise
await addAsync(a, b) // Promise<3>
```
It can also be used to eliminate the call stack:
```ts
// 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 => {
if (i < n) return countAsync(n, i + 1)
return i
})
await countAsync(10000) // 10000
```
#### spawn
```ts
function spawn(
num: number
, create: (id: number) => Awaitable
): Promise
```
A sugar for create multiple values in parallel.
The parameter `id` is from `1` to `num`.
#### limitConcurrencyByQueue
```ts
function limitConcurrencyByQueue(
concurrency: number
, fn: (...args: Args) => PromiseLike
): (...args: Args) => Promise
```
Limit the number of concurrency, calls that exceed the number of concurrency will be delayed in order.
#### reusePendingPromises
```ts
type VerboseResult = [value: T, isReuse: boolean]
interface IReusePendingPromisesOptions {
createKey?: (args: Args) => unknown
verbose?: true
}
function reusePendingPromises(
fn: (...args: Args) => PromiseLike
, options: IReusePendingPromisesOptions & { verbose: true }
): (...args: Args) => Promise>
function reusePendingPromises(
fn: (...args: Args) => PromiseLike
, options: IReusePendingPromisesOptions & { verbose: false }
): (...args: Args) => Promise
function reusePendingPromises(
fn: (...args: Args) => PromiseLike
, options: Omit, 'verbose'>
): (...args: Args) => Promise
function reusePendingPromises(
fn: (...args: Args) => PromiseLike
): (...args: Args) => Promise
```
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
```ts
enum StatefulPromiseState {
Pending = 'pending'
, Fulfilled = 'fulfilled'
, Rejected = 'rejected'
}
class StatefulPromise extends Promise {
static from(promise: PromiseLike): StatefulPromise
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
```ts
class Channel implements IBlockingChannel
```
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 }`.
```ts
const chan = new Channel()
queueMicrotask(() => {
await chan.send('hello')
await chan.send('world')
})
for await (const value of chan.receive()) {
console.log(value)
}
```
#### BufferedChannel
```ts
class BufferedChannel implements IBlockingChannel {
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 }`.
```ts
const chan = new BufferedChannel(1)
queueMicrotask(() => {
await chan.send('hello')
await chan.send('world')
})
for await (const value of chan.receive()) {
console.log(value)
}
```
#### UnlimitedChannel
```ts
class UnlimitedChannel implements INonBlockingChannel
```
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 }`.
```ts
const chan = new UnlimitedChannel()
queueMicrotask(() => {
chan.send('hello')
chan.send('world')
})
for await (const value of chan.receive()) {
console.log(value)
}
```
#### Deferred
```ts
class Deferred implements PromiseLike, IDeferred
```
`Deferred` is a `Promise` that separates `resolve()` and `reject()` from the constructor.
#### MutableDeferred
```ts
class MutableDeferred implements PromiseLike, IDefrred
```
`MutableDeferred` is similar to `Deferred`,
but its `resolve()` and `reject()` can be called multiple times to change the value.
```ts
const deferred = new MutableDeferred()
deferred.resolve(1)
deferred.resolve(2)
await deferred // resolved(2)
```
#### ReusableDeferred
```ts
class ReusableDeferred implements PromiseLike, IDeferred
```
`ReusableDeferred` is similar to `MutableDeferred`,
but its internal `Deferred` will be overwritten with a new pending `Deferred` after each call.
```ts
const deferred = new ReusableDeferred()
deferred.resolve(1)
queueMicrotask(() => deferred.resolve(2))
await deferred // pending, resolved(2)
```
#### DeferredGroup
```ts
class DeferredGroup implements IDeferred {
add(deferred: IDeferred): void
remove(deferred: IDeferred): void
clear(): void
}
```
#### LazyPromise
```ts
class LazyPromise implements PromiseLike {
then: PromiseLike['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
```ts
type Release = () => void
class Semaphore {
constructor(count: number)
acquire(): Promise
acquire(handler: () => Awaitable): Promise
}
```
#### Mutex
```ts
type Release = () => void
class Mutex extends Semaphore {
acquire(): Promise
acquire(handler: () => Awaitable): Promise
}
```
#### DebounceMicrotask
```ts
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
```ts
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
```ts
class TaskRunnerDestroyedError extends CustomError {}
class TaskRunner {
constructor(
concurrency: number = Infinity
, rateLimit?: {
duration: number
limit: number
}
)
/**
* @throws {TaskRunnerDestroyedError}
*/
run(task: (signal: AbortSignal) => Awaitable, signal?: AbortSignal): Promise
destroy(): void
}
```
A task runner, it will execute tasks in FIFO order.