Package Exports
- @flex-development/when
- @flex-development/when/package.json
Readme
:timer_clock: when
like .then, but for synchronous values and thenables.
Contents
What is this?
when is a tiny primitive for chaining callbacks
onto awaitables (synchronous or thenable values).
For thenable values, then is used to invoke the callback after resolution. Otherwise, the callback fires immediately.
This makes it easy to write one code path that supports both synchronous values and promises.
When should I use this?
when is especially useful in libraries implementing awaitable APIs.
It provides Promise.then semantics without forcing Promise.resolve,
preserving synchronous execution whenever possible.
Typical use cases include plugin systems, hook pipelines, module resolvers, data loaders, and file system adapters where users may return both synchronous or asynchronous values.
If you're only dealing with promises and thenables, consider using native async/await or Promise chaining instead.
Why not Promise.resolve?
when preserves synchronous operations whenever possible,
avoiding unnecessary promise allocation and microtask scheduling.
Promise.resolve(value).then(fn) // always a promisewhen(value, fn) // only a promise if `value` is a thenable, or `fn` returns oneDesign guarantees
- Synchronous values remain synchronous
- Thenables are chained without wrapping in
Promise.resolve - No additional microtasks are scheduled for non-thenables
- Failures propagate unless a
failhandler is provided - Returned thenables are preserved without additional wrapping
Install
This package is ESM only.
In Node.js (version 20+) with yarn:
yarn add @flex-development/whenSee Git - Protocols | Yarn for details regarding installing from Git.
In Deno with esm.sh:
import { when } from 'https://esm.sh/@flex-development/when'In browsers with esm.sh:
<script type="module">
import { when } from 'https://esm.sh/@flex-development/when'
</script>Use
Chain a synchronous value
import { isThenable, when, type Awaitable } from '@flex-development/when'
import { ok } from 'devlop'
/**
* The result.
*
* @const {Awaitable<number>} result
*/
const result: Awaitable<number> = when(0, n => n + 1)
ok(!isThenable(result), 'expected `result` to not be thenable')
console.dir(result) // 1Chain a thenable
import { isThenable, when, type Awaitable } from '@flex-development/when'
import { ok } from 'devlop'
/**
* The result.
*
* @const {Awaitable<number>} result
*/
const result: Awaitable<number> = when(Promise.resolve(2), n => n + 1)
ok(isThenable(result), 'expected `result` to be thenable')
console.dir(await result) // 3Pass arguments to the chain callback
When arguments are provided, they are passed to the chain callback first, followed by the resolved value.
When the value passed to when is not thenable, the resolved value is the same value.
import when, { type Awaitable } from '@flex-development/when'
/**
* The result.
*
* @const {Awaitable<number>} result
*/
const result: Awaitable<number> = when(
1, // last argument passed to `Math.min`
Math.min, // `chain`
null, // `fail`
undefined, // `context`
2, // first argument passed to `Math.min`
3, // second argument passed to `Math.min`
4 // third argument passed to `Math.min`
)
console.dir(result) // 1Handle failures
For thenables, the fail callback is passed to then as the onrejected parameter,
and if implemented, to catch as well to prevent unhandled rejections.
import when, { type Awaitable } from '@flex-development/when'
/**
* The thenable value.
*
* @const {PromiseLike<never>} value
*/
const value: PromiseLike<never> = new Promise((resolve, reject) => {
return void reject(new Error('nope', { cause: { url: import.meta.url } }))
})
/**
* The result.
*
* @const {Awaitable<boolean>} result
*/
const result: Awaitable<boolean> = when(value, chain, fail)
console.dir(await result) // false
/**
* @this {void}
*
* @return {true}
* The success result
*/
function chain(this: void): true {
return true
}
/**
* @this {void}
*
* @param {Error} e
* The error to handle
* @return {false}
* The failure result
*/
function fail(this: void, e: Error): false {
return console.dir(e), false
}Bind this context
import when, { type Awaitable } from '@flex-development/when'
/**
* The `this` context.
*/
type Context = { prefix: string }
/**
* The result.
*
* @const {Awaitable<string>} result
*/
const result: Awaitable<string> = when(13, id, null, { prefix: 'id:' })
console.log(result) // 'id:13'
/**
* @this {Context}
*
* @param {number | string} num
* The id number
* @return {string}
* The id string
*/
function id(this: Context, num: number | string): string {
return this.prefix + num
}Use an options object
import when, { type Awaitable } from '@flex-development/when'
/**
* The `this` context.
*/
type Context = { errors: Error[] }
/**
* The thenable value.
*
* @const {Promise<number>} value
*/
const value: Promise<number> = new Promise(resolve => resolve(3))
/**
* The result.
*
* @const {Awaitable<number | undefined>} result
*/
const result: Awaitable<number | undefined> = when(value, {
args: [39],
chain: divide,
context: { errors: [] },
fail
})
console.dir(await result) // 13
/**
* @this {void}
*
* @param {number} dividend
* The number to divide
* @param {number} divisor
* The number to divide by
* @return {number}
* The quotient
*/
function divide(this: void, dividend: number, divisor: number): number {
return dividend / divisor
}
/**
* @this {Context}
*
* @param {Error} e
* The error to handle
* @return {undefined}
*/
function fail(this: Context, e: Error): undefined {
return void this.errors.push(e)
}API
when exports the identifiers listed below.
The default export is when.
isPromise<T>(value)
Check if value looks like a Promise.
👉 Note: This function intentionally performs a structural check instead of a brand check. It does not rely on
instanceof Promiseor constructors, making it compatible with cross-realm promises and custom thenables.
Type Parameters
T(any) — the resolved value
Parameters
value(unknown) — the thing to check
Returns
(value is Promise<T>) true if value is a thenable with a catch method, false otherwise
isThenable<T>(value)
Check if value looks like a thenable, i.e. a PromiseLike object.
👉 Note: Also exported as
isPromiseLike.
Type Parameters
T(any) — the resolved value
Parameters
value(unknown) — the thing to check
Returns
(value is PromiseLike<T>) true if value is an object or function with a then method, false otherwise
when<T[, Next][, Failure][, Args][, Error][, This]>(value, chain[, fail][, context][, ...args])
Chain a callback, calling the function after value is resolved,
or immediately if value is not a thenable.
Overloads
function when<
T,
Next = any,
Args extends any[] = any[],
This = unknown
>(
this: void,
value: Awaitable<T>,
chain: Chain<T, Next, Args, This>,
fail?: null | undefined,
context?: This | null | undefined,
...args: Args
): Awaitable<Next>function when<
T,
Next = any,
Failure = Next,
Args extends any[] = any[],
Error = any,
This = unknown
>(
this: void,
value: Awaitable<T>,
chain: Chain<T, Next, Args, This>,
fail?: Fail<Failure, Error, This> | null | undefined,
context?: This | null | undefined,
...args: Args
): Awaitable<Failure | Next>function when<
T,
Next = any,
Failure = Next,
Args extends any[] = any[],
Error = any,
This = unknown
>(
this: void,
value: Awaitable<T>,
chain: Options<T, Next, Failure, Args, Error, This>
): Awaitable<Failure | Next>Type Parameters
T(any) — the previously resolved valueNext(any, optional) — the next resolved value- default:
any
- default:
Failure(any, optional) — the next resolved value on failure- default:
Next
- default:
Args(readonly any[], optional) — the chain function arguments- default:
any[]
- default:
Error(any, optional) — the error to possibly handle- default:
any
- default:
This(any, optional) — thethiscontext- default:
unknown
- default:
Parameters
value(Awaitable<T>) — the current awaitablechain(Chain<T, Next, Args, This>|Options<T, Next, Failure, Args, Error, This>) — the chain callback or options for chainingfail(Fail<Failure, Error, This>|null|undefined) — the callback to fire when a failure occurs. failures include:- rejections of the input thenable
- rejections returned from
chain - synchronous errors thrown in
chain
if nofailhandler is provided, failures are re-thrown or re-propagated.👉 note: for thenables, this callback is passed to
thenas theonrejectedparameter, and if implemented, tocatchas well to prevent unhandled rejections.
context(This|null|undefined) — thethiscontext of the chain andfailcallbacks...args(Args) — the arguments to pass to the chain callback
Returns
(Awaitable<Failure | Next> | Awaitable<Next>) The next awaitable
Types
This package is fully typed with TypeScript.
Awaitable<T>
A synchronous or thenable value (type).
type Awaitable<T> = PromiseLike<T> | TType Parameters
T(any) — the resolved value
Chain<[T][, Next][, Args][, This]>
A chain callback (type).
type Chain<
T = any,
Next = any,
Args extends readonly any[] = any[],
This = unknown
> = (this: This, ...params: [...Args, T]) => Awaitable<Next>Type Parameters
T(any, optional) — the previously resolved value- default:
any
- default:
Next(any, optional) — the next resolved value- default:
any
- default:
Args(readonly any[], optional) — the function arguments- default:
any[]
- default:
This(any, optional) — thethiscontext- default:
unknown
- default:
Parameters
this(This)...params([...Args, T]) — the function parameters, with the last being the previously resolved value. in cases where a promise is not being resolved, this is the samevaluepassed towhen
Returns
(Awaitable<Next>) The next awaitable
Fail<[Next][, Error][, This]>
The callback to fire when a failure occurs (type).
type Fail<
Next = any,
Error = any,
This = unknown
> = (this: This, e: Error) => Awaitable<Next>Type Parameters
Next(any, optional) — the next resolved value- default:
any
- default:
Error(any, optional) — the error to handle- default:
any
- default:
This(any, optional) — thethiscontext- default:
unknown
- default:
Parameters
this(This)e(Error) — the error
Returns
(Awaitable<Next>) The next awaitable
Options<[T][, Next][, Failure][, Args][, Error][, This]>
Options for chaining (interface).
interface Options<
T = any,
Next = any,
Failure = Next,
Args extends readonly any[] = any[],
Error = any,
This = any
> { /* ... */ }Type Parameters
T(any, optional) — the previously resolved value- default:
any
- default:
Next(any, optional) — the next resolved value- default:
any
- default:
Failure(any, optional) — the next resolved value on failure- default:
Next
- default:
Args(readonly any[], optional) — the chain function arguments- default:
any[]
- default:
Error(any, optional) — the error to possibly handle- default:
any
- default:
This(any, optional) — thethiscontext- default:
any
- default:
Properties
args?(Args|null|undefined) — the arguments to pass to thechaincallbackchain(Chain<T, Next, Args, This>) — the chain callbackcontext?(This|null|undefined) — thethiscontext of thechainandfailcallbacksfail?(Fail<Next, Error, This>|null|undefined) — the callback to fire when a failure occurs. failures include:- rejections of the input thenable
- rejections returned from
chain - synchronous errors thrown in
chain
if nofailhandler is provided, failures are re-thrown or re-propagated.👉 note: for thenables, this callback is passed to
thenas theonrejectedparameter, and if implemented, tocatchas well to prevent unhandled rejections.
Glossary
awaitable
A synchronous or thenable value.
thenable
An object or function with a then method.
JavaScript engines use duck-typing for promises.
Arrays, functions, and objects with a then method will be treated as promise-like objects, and work with built-in
mechanisms like Promise.resolve and the await keyword like native promises.
Some thenables also implement a catch method (like native promises).
When available, when uses it to ensure rejections are handled.
Project
Version
when adheres to semver.
Contribute
See CONTRIBUTING.md.
This project has a code of conduct. By interacting with this repository, organization, or community you agree to abide by its terms.
Sponsor
This package is intentionally small — and intentionally maintained.
Small primitives power larger systems. Support long-term stability by sponsoring Flex Development.