@flex-development/when 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## [2.0.0](https://github.com/flex-development/when/compare/1.0.0...2.0.0) (2026-02-19)
2
+
3
+ ### ⚠ BREAKING CHANGES
4
+
5
+ - `catch` support
6
+
7
+ ### :package: Build
8
+
9
+ - [[`dd93b19`](https://github.com/flex-development/when/commit/dd93b19904b21760b5ae86a48fe7b7127adbcf95)] **deps-dev:** Bump @commitlint/cli from 20.4.1 to 20.4.2 in the commitlint group ([#9](https://github.com/flex-development/when/issues/9))
10
+ - [[`9229069`](https://github.com/flex-development/when/commit/9229069b28f05bf3394c29d85ec3261c6246959b)] **deps-dev:** Bump happy-dom from 20.6.2 to 20.6.3 ([#10](https://github.com/flex-development/when/issues/10))
11
+
12
+ ### :sparkles: Features
13
+
14
+ - [[`8d8f17f`](https://github.com/flex-development/when/commit/8d8f17f847ac90d5633d87c554229bbd42d9a1cf)] `catch` support
15
+ - [[`003be0f`](https://github.com/flex-development/when/commit/003be0f34d1e626b0ee6ea940d73f0babd12df78)] **lib:** `isPromise`
16
+
1
17
  ## 1.0.0 (2026-02-18)
2
18
 
3
19
  ### :package: Build
@@ -71,3 +87,4 @@
71
87
 
72
88
 
73
89
 
90
+
package/README.md CHANGED
@@ -17,44 +17,72 @@ like `.then`, but for synchronous values *and* thenables.
17
17
  ## Contents
18
18
 
19
19
  - [What is this?](#what-is-this)
20
+ - [When should I use this?](#when-should-i-use-this)
20
21
  - [Why not `Promise.resolve`?](#why-not-promiseresolve)
22
+ - [Design guarantees](#design-guarantees)
21
23
  - [Install](#install)
22
24
  - [Use](#use)
23
25
  - [Chain a synchronous value](#chain-a-synchronous-value)
24
26
  - [Chain a *thenable*](#chain-a-thenable)
25
27
  - [Pass arguments to the chain callback](#pass-arguments-to-the-chain-callback)
26
- - [Handle rejections / thrown errors](#handle-rejections--thrown-errors)
28
+ - [Handle failures](#handle-failures)
27
29
  - [Bind `this` context](#bind-this-context)
28
30
  - [Use an options object](#use-an-options-object)
29
31
  - [API](#api)
32
+ - [`isPromise<T>(value)`][ispromise]
30
33
  - [`isThenable<T>(value)`][isthenable]
31
- - [`when<[T][, Next][, Args][, Self]>(value, chain[, reject][, context][, ...args])`][when]
34
+ - [`when<T[, Next][, Failure][, Args][, Error][, This]>(value, chain[, reject][, context][, ...args])`][when]
32
35
  - [Types](#types)
33
36
  - [`Awaitable<T>`][awaitable]
34
- - [`Chain<[T][, Next][, Args][, Self]>`][chain]
35
- - [`Options<[T][, Next][, Args][, Self]>`][options]
36
- - [`Reject<[Next][, Fail][, Self]>`][reject]
37
+ - [`Chain<[T][, Next][, Args][, This]>`][chain]
38
+ - [`Fail<[Next][, Error][, This]>`][fail]
39
+ - [`Options<[T][, Next][, Failure][, Args][, Error][, This]>`][options]
37
40
  - [Glossary](#glossary)
38
41
  - [Project](#project)
39
42
  - [Version](#version)
40
43
  - [Contribute](#contribute)
44
+ - [Sponsor](#sponsor)
41
45
 
42
46
  ## What is this?
43
47
 
44
- `when` is a small, but useful package for chaining a callback
45
- onto an [awaitable][] (a value or a [*thenable*][thenable]).
48
+ `when` is a tiny primitive for chaining callbacks
49
+ onto [awaitables][awaitable] (synchronous or [*thenable*][thenable] values).
46
50
 
47
- For thenable values, `.then` is used to invoke the callback after resolution.
48
- Otherwise, the callback is called immediately.
49
- This makes it easy to write one code path that supports both synchronous and asynchronous values.
51
+ For thenable values, `then` is used to invoke the callback after resolution. Otherwise, the callback fires immediately.
52
+ This makes it easy to write one code path that supports both synchronous values and promises.
50
53
 
51
- `when` is especially useful in libraries supporting awaitable APIs or libraries that accept user-provided hooks,
52
- loaders, or resolvers that may or may not return promises.
54
+ ## When should I use this?
53
55
 
54
- ### Why not `Promise.resolve`?
56
+ `when` is especially useful in libraries implementing awaitable APIs.
55
57
 
56
- `when` preserves synchronous values when possible, thus avoiding microtask scheduling and needless promise-allocation
57
- for these values, as well as making it a great choice for libraries offering API flexibility.
58
+ It provides [`Promise.then`][promise-then] semantics without forcing [`Promise.resolve`][promise-resolve],
59
+ preserving synchronous execution whenever possible.
60
+
61
+ Typical use cases include plugin systems, hook pipelines, module resolvers, data loaders, and file system adapters where
62
+ users may return both synchronous or asynchronous values.
63
+
64
+ If you're only dealing with promises and thenables, consider using native `async/await` or `Promise` chaining instead.
65
+
66
+ ### Why not [`Promise.resolve`][promise-resolve]?
67
+
68
+ `when` preserves synchronous operations whenever possible,
69
+ avoiding unnecessary promise allocation and microtask scheduling.
70
+
71
+ ```ts
72
+ Promise.resolve(value).then(fn) // always a promise
73
+ ```
74
+
75
+ ```ts
76
+ when(value, fn) // only a promise if `value` is a thenable, or `fn` returns one
77
+ ```
78
+
79
+ ### Design guarantees
80
+
81
+ - Synchronous values remain synchronous
82
+ - [*Thenable*s][thenable] are chained without wrapping in [`Promise.resolve`][promise-resolve]
83
+ - No additional microtasks are scheduled for non-thenables
84
+ - Failures propagate unless a `fail` handler is provided
85
+ - Returned thenables are preserved without additional wrapping
58
86
 
59
87
  ## Install
60
88
 
@@ -125,7 +153,7 @@ console.dir(await result) // 3
125
153
 
126
154
  ### Pass arguments to the chain callback
127
155
 
128
- Arguments are passed first, and the resolved value is passed last.
156
+ When arguments are provided, they are passed to the `chain` callback first, followed by the resolved value.
129
157
 
130
158
  When the `value` passed to `when` is not [*thenable*][thenable], the resolved value is the same `value`.
131
159
 
@@ -137,12 +165,23 @@ import when, { type Awaitable } from '@flex-development/when'
137
165
  *
138
166
  * @const {Awaitable<number>} result
139
167
  */
140
- const result: Awaitable<number> = when(1, Math.min, null, undefined, 2, 3, 4)
168
+ const result: Awaitable<number> = when(
169
+ 1, // last argument passed to `Math.min`
170
+ Math.min, // `chain`
171
+ null, // `fail`
172
+ undefined, // `context`
173
+ 2, // first argument passed to `Math.min`
174
+ 3, // second argument passed to `Math.min`
175
+ 4 // third argument passed to `Math.min`
176
+ )
141
177
 
142
178
  console.dir(result) // 1
143
179
  ```
144
180
 
145
- ### Handle rejections / thrown errors
181
+ ### Handle failures
182
+
183
+ For [*thenable*s][thenable], the `fail` callback is passed to `then` as the `onrejected` parameter,
184
+ and if implemented, to `catch` as well to prevent unhandled rejections.
146
185
 
147
186
  ```ts
148
187
  import when, { type Awaitable } from '@flex-development/when'
@@ -161,7 +200,7 @@ const value: PromiseLike<never> = new Promise((resolve, reject) => {
161
200
  *
162
201
  * @const {Awaitable<boolean>} result
163
202
  */
164
- const result: Awaitable<boolean> = when(value, chain, reject)
203
+ const result: Awaitable<boolean> = when(value, chain, fail)
165
204
 
166
205
  console.dir(await result) // false
167
206
 
@@ -183,7 +222,7 @@ function chain(this: void): true {
183
222
  * @return {false}
184
223
  * The failure result
185
224
  */
186
- function reject(this: void, e: Error): false {
225
+ function fail(this: void, e: Error): false {
187
226
  return console.dir(e), false
188
227
  }
189
228
  ```
@@ -246,7 +285,7 @@ const result: Awaitable<number | undefined> = when(value, {
246
285
  args: [39],
247
286
  chain: divide,
248
287
  context: { errors: [] },
249
- reject
288
+ fail
250
289
  })
251
290
 
252
291
  console.dir(await result) // 13
@@ -272,7 +311,7 @@ function divide(this: void, dividend: number, divisor: number): number {
272
311
  * The error to handle
273
312
  * @return {undefined}
274
313
  */
275
- function reject(this: Context, e: Error): undefined {
314
+ function fail(this: Context, e: Error): undefined {
276
315
  return void this.errors.push(e)
277
316
  }
278
317
  ```
@@ -283,9 +322,31 @@ function reject(this: Context, e: Error): undefined {
283
322
 
284
323
  The default export is [`when`][when].
285
324
 
325
+ ### `isPromise<T>(value)`
326
+
327
+ Check if `value` looks like a `Promise`.
328
+
329
+ > 👉 **Note**: This function intentionally performs a structural check instead of a brand check.
330
+ > It does not rely on `instanceof Promise` or constructors, making it compatible with cross-realm promises
331
+ > and custom thenables.
332
+
333
+ #### Type Parameters
334
+
335
+ - `T` (`any`)
336
+ — the resolved value
337
+
338
+ #### Parameters
339
+
340
+ - `value` (`unknown`)
341
+ — the thing to check
342
+
343
+ #### Returns
344
+
345
+ (`value is Promise<T>`) `true` if `value` is a [*thenable*][thenable] with a `catch` method, `false` otherwise
346
+
286
347
  ### `isThenable<T>(value)`
287
348
 
288
- Check if `value` looks like a [*thenable*][thenable].
349
+ Check if `value` looks like a [*thenable*][thenable], i.e. a `PromiseLike` object.
289
350
 
290
351
  > 👉 **Note**: Also exported as `isPromiseLike`.
291
352
 
@@ -301,15 +362,16 @@ Check if `value` looks like a [*thenable*][thenable].
301
362
 
302
363
  #### Returns
303
364
 
304
- (`value is PromiseLike<T>`) `true` if `value` is a thenable, `false` otherwise
365
+ (`value is PromiseLike<T>`) `true` if `value` is an object or function with a `then` method, `false` otherwise
305
366
 
306
367
  <!--lint disable-->
307
368
 
308
- ### `when<[T][, Next][, Args][, Self]>(value, chain[, reject][, context][, ...args])`
369
+ ### `when<T[, Next][, Failure][, Args][, Error][, This]>(value, chain[, fail][, context][, ...args])`
309
370
 
310
371
  <!--lint enable-->
311
372
 
312
- Chain a callback, calling the function after `value` is resolved, or immediately if `value` is not thenable.
373
+ Chain a callback, calling the function after `value` is resolved,
374
+ or immediately if `value` is not a [*thenable*][thenable].
313
375
 
314
376
  #### Overloads
315
377
 
@@ -318,13 +380,13 @@ function when<
318
380
  T,
319
381
  Next = any,
320
382
  Args extends any[] = any[],
321
- Self = unknown
383
+ This = unknown
322
384
  >(
323
385
  this: void,
324
386
  value: Awaitable<T>,
325
- chain: Chain<T, Next, Args, Self>,
326
- reject?: Reject<Next, any, Self> | null | undefined,
327
- context?: Self | null | undefined,
387
+ chain: Chain<T, Next, Args, This>,
388
+ fail?: null | undefined,
389
+ context?: This | null | undefined,
328
390
  ...args: Args
329
391
  ): Awaitable<Next>
330
392
  ```
@@ -333,13 +395,33 @@ function when<
333
395
  function when<
334
396
  T,
335
397
  Next = any,
398
+ Failure = Next,
336
399
  Args extends any[] = any[],
337
- Self = unknown
400
+ Error = any,
401
+ This = unknown
338
402
  >(
339
403
  this: void,
340
404
  value: Awaitable<T>,
341
- chain: Options<T, Next, Args, Self>
342
- ): Awaitable<Next>
405
+ chain: Chain<T, Next, Args, This>,
406
+ fail?: Fail<Failure, Error, This> | null | undefined,
407
+ context?: This | null | undefined,
408
+ ...args: Args
409
+ ): Awaitable<Failure | Next>
410
+ ```
411
+
412
+ ```ts
413
+ function when<
414
+ T,
415
+ Next = any,
416
+ Failure = Next,
417
+ Args extends any[] = any[],
418
+ Error = any,
419
+ This = unknown
420
+ >(
421
+ this: void,
422
+ value: Awaitable<T>,
423
+ chain: Options<T, Next, Failure, Args, Error, This>
424
+ ): Awaitable<Failure | Next>
343
425
  ```
344
426
 
345
427
  #### Type Parameters
@@ -349,29 +431,41 @@ function when<
349
431
  - `Next` (`any`, optional)
350
432
  — the next resolved value
351
433
  - **default**: `any`
434
+ - `Failure` (`any`, optional)
435
+ — the next resolved value on failure
436
+ - **default**: `Next`
352
437
  - `Args` (`readonly any[]`, optional)
353
- — the function arguments
438
+ — the chain function arguments
354
439
  - **default**: `any[]`
355
- - `Self` (`any`, optional)
440
+ - `Error` (`any`, optional)
441
+ — the error to possibly handle
442
+ - **default**: `any`
443
+ - `This` (`any`, optional)
356
444
  — the `this` context
357
445
  - **default**: `unknown`
358
446
 
359
447
  #### Parameters
360
448
 
361
449
  - `value` ([`Awaitable<T>`][awaitable])
362
- — the promise or the resolved value
363
- - `chain` ([`Chain<T, Next, Args, Self>`][chain] | [`Options<T, Next, Args, Self>`][options])
450
+ — the current [*awaitable*][awaitable-term]
451
+ - `chain` ([`Chain<T, Next, Args, This>`][chain] | [`Options<T, Next, Failure, Args, Error, This>`][options])
364
452
  — the chain callback or options for chaining
365
- - `reject` ([`Reject<Next, any, Self>`][reject] | `null` | `undefined`)
366
- — the callback to fire when a promise is rejected or an error is thrown
367
- - `context` (`Self` | `null` | `undefined`)
368
- the `this` context of the chain and error callbacks
453
+ - `fail` ([`Fail<Failure, Error, This>`][fail] | `null` | `undefined`)
454
+ — the callback to fire when a failure occurs. failures include:
455
+ - rejections of the input [*thenable*][thenable]
456
+ - rejections returned from `chain`
457
+ - synchronous errors thrown in `chain`\
458
+ if no `fail` handler is provided, failures are re-thrown or re-propagated.
459
+ > 👉 **note**: for thenables, this callback is passed to `then` as the `onrejected` parameter,
460
+ > and if implemented, to `catch` as well to prevent unhandled rejections.
461
+ - `context` (`This` | `null` | `undefined`)
462
+ — the `this` context of the chain and `fail` callbacks
369
463
  - `...args` (`Args`)
370
464
  — the arguments to pass to the chain callback
371
465
 
372
466
  #### Returns
373
467
 
374
- ([`Awaitable<Next>`][awaitable]) The next promise or value
468
+ ([`Awaitable<Failure | Next>`][awaitable] | [`Awaitable<Next>`][awaitable]) The next [*awaitable*][awaitable-term]
375
469
 
376
470
  ## Types
377
471
 
@@ -390,7 +484,7 @@ type Awaitable<T> = PromiseLike<T> | T
390
484
  - `T` (`any`)
391
485
  — the resolved value
392
486
 
393
- ### `Chain<[T][, Next][, Args][, Self]>`
487
+ ### `Chain<[T][, Next][, Args][, This]>`
394
488
 
395
489
  A chain callback (`type`).
396
490
 
@@ -399,8 +493,8 @@ type Chain<
399
493
  T = any,
400
494
  Next = any,
401
495
  Args extends readonly any[] = any[],
402
- Self = unknown
403
- > = (this: Self, ...params: [...Args, T]) => Awaitable<Next>
496
+ This = unknown
497
+ > = (this: This, ...params: [...Args, T]) => Awaitable<Next>
404
498
  ```
405
499
 
406
500
  #### Type Parameters
@@ -414,93 +508,107 @@ type Chain<
414
508
  - `Args` (`readonly any[]`, optional)
415
509
  — the function arguments
416
510
  - **default**: `any[]`
417
- - `Self` (`any`, optional)
511
+ - `This` (`any`, optional)
418
512
  — the `this` context
419
513
  - **default**: `unknown`
420
514
 
421
515
  #### Parameters
422
516
 
423
- - **`this`** (`Self`)
517
+ - **`this`** (`This`)
424
518
  - `...params` (`[...Args, T]`)
425
519
  — the function parameters, with the last being the previously resolved value.
426
520
  in cases where a promise is not being resolved, this is the same `value` passed to `when`
427
521
 
428
522
  #### Returns
429
523
 
430
- ([`Awaitable<Next>`][awaitable]) The next promise or value
524
+ ([`Awaitable<Next>`][awaitable]) The next [*awaitable*][awaitable-term]
431
525
 
432
- ### `Options<[T][, Next][, Args][, Self]>`
526
+ ### `Fail<[Next][, Error][, This]>`
433
527
 
434
- Options for chaining (`interface`).
528
+ The callback to fire when a failure occurs (`type`).
435
529
 
436
530
  ```ts
437
- interface Options<
438
- T = any,
531
+ type Fail<
439
532
  Next = any,
440
- Args extends readonly any[] = any[],
441
- Self = any
442
- > { /* ... */ }
533
+ Error = any,
534
+ This = unknown
535
+ > = (this: This, e: Error) => Awaitable<Next>
443
536
  ```
444
537
 
445
538
  #### Type Parameters
446
539
 
447
- - `T` (`any`, optional)
448
- — the previously resolved value
449
- - **default**: `any`
450
540
  - `Next` (`any`, optional)
451
541
  — the next resolved value
452
542
  - **default**: `any`
453
- - `Args` (`readonly any[]`, optional)
454
- — the chain function arguments
455
- - **default**: `any[]`
456
- - `Self` (`any`, optional)
457
- — the `this` context
543
+ - `Error` (`any`, optional)
544
+ — the error to handle
458
545
  - **default**: `any`
546
+ - `This` (`any`, optional)
547
+ — the `this` context
548
+ - **default**: `unknown`
459
549
 
460
- #### Properties
550
+ #### Parameters
461
551
 
462
- - `args?` (`Args` | `null` | `undefined`)
463
- the arguments to pass to the `chain` callback
464
- - `chain` ([`Chain<T, Next, Args, Self>`][chain])
465
- — the chain callback
466
- - `context?` (`Self` | `null` | `undefined`)
467
- — the `this` context of the `chain` and `reject` callbacks
468
- - `reject?` ([`Reject<Next, any, Self>`][reject] | `null` | `undefined`)
469
- — the callback to fire when a promise is rejected or an error is thrown
552
+ - **`this`** (`This`)
553
+ - `e` (`Error`)
554
+ the error
555
+
556
+ #### Returns
557
+
558
+ ([`Awaitable<Next>`][awaitable]) The next [*awaitable*][awaitable-term]
470
559
 
471
- ### `Reject<[Next][, Fail][, Self]>`
560
+ ### `Options<[T][, Next][, Failure][, Args][, Error][, This]>`
472
561
 
473
- The callback to fire when a promise is rejected or an error is thrown from a synchronous function (`type`).
562
+ Options for chaining (`interface`).
474
563
 
475
564
  ```ts
476
- type Reject<
565
+ interface Options<
566
+ T = any,
477
567
  Next = any,
478
- Fail = any,
479
- Self = unknown
480
- > = (this: Self, e: Fail) => Awaitable<Next>
568
+ Failure = Next,
569
+ Args extends readonly any[] = any[],
570
+ Error = any,
571
+ This = any
572
+ > { /* ... */ }
481
573
  ```
482
574
 
483
575
  #### Type Parameters
484
576
 
577
+ - `T` (`any`, optional)
578
+ — the previously resolved value
579
+ - **default**: `any`
485
580
  - `Next` (`any`, optional)
486
581
  — the next resolved value
487
582
  - **default**: `any`
488
- - `Fail` (`any`, optional)
489
- — the error to handle
583
+ - `Failure` (`any`, optional)
584
+ — the next resolved value on failure
585
+ - **default**: `Next`
586
+ - `Args` (`readonly any[]`, optional)
587
+ — the chain function arguments
588
+ - **default**: `any[]`
589
+ - `Error` (`any`, optional)
590
+ — the error to possibly handle
490
591
  - **default**: `any`
491
- - `Self` (`any`, optional)
592
+ - `This` (`any`, optional)
492
593
  — the `this` context
493
- - **default**: `unknown`
494
-
495
- #### Parameters
496
-
497
- - **`this`** (`Self`)
498
- - `e` (`Fail`)
499
- — the error
594
+ - **default**: `any`
500
595
 
501
- #### Returns
596
+ #### Properties
502
597
 
503
- ([`Awaitable<Next>`][awaitable]) The next promise or value
598
+ - `args?` (`Args` | `null` | `undefined`)
599
+ — the arguments to pass to the `chain` callback
600
+ - `chain` ([`Chain<T, Next, Args, This>`][chain])
601
+ — the chain callback
602
+ - `context?` (`This` | `null` | `undefined`)
603
+ — the `this` context of the `chain` and `fail` callbacks
604
+ - `fail?` ([`Fail<Next, Error, This>`][fail] | `null` | `undefined`)
605
+ — the callback to fire when a failure occurs. failures include:
606
+ - rejections of the input [*thenable*][thenable]
607
+ - rejections returned from `chain`
608
+ - synchronous errors thrown in `chain`\
609
+ if no `fail` handler is provided, failures are re-thrown or re-propagated.
610
+ > 👉 **note**: for thenables, this callback is passed to `then` as the `onrejected` parameter,
611
+ > and if implemented, to `catch` as well to prevent unhandled rejections.
504
612
 
505
613
  ## Glossary
506
614
 
@@ -510,11 +618,14 @@ A synchronous or [*thenable*][thenable] value.
510
618
 
511
619
  ### *thenable*
512
620
 
513
- An object or function with a `.then` method.
621
+ An object or function with a `then` method.
514
622
 
515
623
  JavaScript engines use duck-typing for promises.
516
- Structures with a `.then` method will be treated as promise-like objects, and work with built-in mechanisms
517
- like [`Promise.resolve`][promise-resolve] and the [`await` keyword][await] like native promises.
624
+ Arrays, functions, and objects with a `then` method will be treated as promise-like objects, and work with built-in
625
+ mechanisms like [`Promise.resolve`][promise-resolve] and the [`await` keyword][await] like native promises.
626
+
627
+ Some thenables also implement a `catch` method (like native promises).
628
+ When available, `when` uses it to ensure rejections are handled.
518
629
 
519
630
  ## Project
520
631
 
@@ -529,23 +640,36 @@ See [`CONTRIBUTING.md`](CONTRIBUTING.md).
529
640
  This project has a [code of conduct](./CODE_OF_CONDUCT.md).
530
641
  By interacting with this repository, organization, or community you agree to abide by its terms.
531
642
 
643
+ ### Sponsor
644
+
645
+ This package is intentionally small — and intentionally maintained.
646
+
647
+ Small primitives power larger systems.
648
+ Support long-term stability by sponsoring Flex Development.
649
+
532
650
  [await]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/await
533
651
 
652
+ [awaitable-term]: #awaitable
653
+
534
654
  [awaitable]: #awaitablet
535
655
 
536
- [chain]: #chaint-next-args-self
656
+ [chain]: #chaint-next-args-this
537
657
 
538
658
  [esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
539
659
 
540
660
  [esmsh]: https://esm.sh
541
661
 
662
+ [fail]: #failnext-error-this
663
+
664
+ [ispromise]: #ispromisetvalue
665
+
542
666
  [isthenable]: #isthenabletvalue
543
667
 
544
- [options]: #optionst-next-args-self
668
+ [options]: #optionst-next-failure-args-error-this
545
669
 
546
670
  [promise-resolve]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
547
671
 
548
- [reject]: #rejectnext-fail-self
672
+ [promise-then]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
549
673
 
550
674
  [semver]: https://semver.org
551
675
 
@@ -553,6 +677,6 @@ By interacting with this repository, organization, or community you agree to abi
553
677
 
554
678
  [typescript]: https://www.typescriptlang.org
555
679
 
556
- [when]: #whent-next-args-selfvalue-chain-reject-context-args
680
+ [when]: #whent-next-failure-args-error-thisvalue-chain-fail-context-args
557
681
 
558
682
  [yarn]: https://yarnpkg.com
package/dist/index.d.mts CHANGED
@@ -10,12 +10,16 @@
10
10
  * The previously resolved value
11
11
  * @template {any} [Next=any]
12
12
  * The next resolved value
13
+ * @template {any} [Failure=Next]
14
+ * The next resolved value on failure
13
15
  * @template {ReadonlyArray<any>} [Args=any[]]
14
16
  * The chain function arguments
15
- * @template {any} [Self=any]
17
+ * @template {any} [Error=any]
18
+ * The error to possibly handle
19
+ * @template {any} [This=any]
16
20
  * The `this` context
17
21
  */
18
- interface Options<T = any, Next = any, Args extends readonly any[] = any[], Self = any> {
22
+ interface Options<T = any, Next = any, Failure = Next, Args extends readonly any[] = any[], Error = any, This = any> {
19
23
  /**
20
24
  * The arguments to pass to the {@linkcode chain} callback.
21
25
  */
@@ -23,28 +27,74 @@ interface Options<T = any, Next = any, Args extends readonly any[] = any[], Self
23
27
  /**
24
28
  * The chain callback.
25
29
  *
30
+ * > 👉 **Note**: For thenables, this callback is passed to `then` as
31
+ * > the `onfulfilled` parameter.
32
+ *
26
33
  * @see {@linkcode Chain}
34
+ * @see {@linkcode PromiseLike.then}
27
35
  */
28
- chain: Chain<T, Next, Args, Self>;
36
+ chain: Chain<T, Next, Args, This>;
29
37
  /**
30
- * The `this` context of the {@linkcode chain}
31
- * and {@linkcode reject} callbacks.
38
+ * The `this` context of the {@linkcode chain} and {@linkcode fail} callbacks.
32
39
  */
33
- context?: Self | null | undefined;
40
+ context?: This | null | undefined;
34
41
  /**
35
- * The callback to fire when a promise is rejected or an error is thrown.
42
+ * The callback to fire when a failure occurs.
43
+ *
44
+ * Failures include:
45
+ *
46
+ * - Rejections of the input thenable
47
+ * - Rejections returned from {@linkcode chain}
48
+ * - Synchronous errors thrown in {@linkcode chain}
49
+ *
50
+ * If no `fail` handler is provided, failures are re-thrown or re-propagated.
51
+ *
52
+ * > 👉 **Note**: For thenables, this callback is passed to `then` as
53
+ * > the `onrejected` parameter, and if implemented, to `catch` as well
54
+ * > to prevent unhandled rejections.
55
+ *
56
+ * @see {@linkcode Fail}
57
+ * @see {@linkcode Promise.catch}
58
+ * @see {@linkcode PromiseLike.then}
36
59
  *
37
- * @see {@linkcode Reject}
60
+ * @since 2.0.0
38
61
  */
39
- reject?: Reject<Next, any, Self> | null | undefined;
62
+ fail?: Fail<Failure, Error, This> | null | undefined;
40
63
  }
41
64
 
65
+ /**
66
+ * @file isPromise
67
+ * @module when/lib/isPromise
68
+ */
69
+ /**
70
+ * Check if `value` looks like a {@linkcode Promise}.
71
+ *
72
+ * > 👉 **Note**: This function intentionally performs a structural check
73
+ * > instead of a brand check.
74
+ * > It does not rely on `instanceof Promise` or constructors, making it
75
+ * > compatible with cross-realm promises and custom thenables.
76
+ *
77
+ * @see {@linkcode isThenable}
78
+ *
79
+ * @template {any} T
80
+ * The resolved value
81
+ *
82
+ * @this {void}
83
+ *
84
+ * @param {unknown} value
85
+ * The thing to check
86
+ * @return {value is Promise<T>}
87
+ * `true` if `value` is a thenable with a `catch` method, `false` otherwise
88
+ */
89
+ declare function isPromise<T>(this: void, value: unknown): value is Promise<T>;
90
+
42
91
  /**
43
92
  * @file isThenable
44
93
  * @module when/lib/isThenable
45
94
  */
46
95
  /**
47
- * Check if `value` looks like a thenable.
96
+ * Check if `value` looks like a thenable,
97
+ * i.e. a {@linkcode PromiseLike} object.
48
98
  *
49
99
  * @template {any} T
50
100
  * The resolved value
@@ -54,7 +104,8 @@ interface Options<T = any, Next = any, Args extends readonly any[] = any[], Self
54
104
  * @param {unknown} value
55
105
  * The thing to check
56
106
  * @return {value is PromiseLike<T>}
57
- * `true` if `value` is a thenable, `false` otherwise
107
+ * `true` if `value` is an object or function with a `then` method,
108
+ * `false` otherwise
58
109
  */
59
110
  declare function isThenable<T>(this: void, value: unknown): value is PromiseLike<T>;
60
111
 
@@ -65,11 +116,11 @@ declare function isThenable<T>(this: void, value: unknown): value is PromiseLike
65
116
 
66
117
  /**
67
118
  * Chain a callback, calling the function after `value` is resolved,
68
- * or immediately if `value` is not thenable.
119
+ * or immediately if `value` is not a thenable.
69
120
  *
70
121
  * @see {@linkcode Awaitable}
71
122
  * @see {@linkcode Chain}
72
- * @see {@linkcode Reject}
123
+ * @see {@linkcode Fail}
73
124
  *
74
125
  * @template {any} T
75
126
  * The previously resolved value
@@ -77,28 +128,65 @@ declare function isThenable<T>(this: void, value: unknown): value is PromiseLike
77
128
  * The next resolved value
78
129
  * @template {ReadonlyArray<any>} [Args=any[]]
79
130
  * The chain function arguments
80
- * @template {any} [Self=any]
131
+ * @template {any} [This=any]
81
132
  * The `this` context
82
133
  *
83
134
  * @this {void}
84
135
  *
85
136
  * @param {Awaitable<T>} value
86
- * The promise or the resolved value
87
- * @param {Chain<T, Next, Args, Self>} chain
137
+ * The current awaitable
138
+ * @param {Chain<T, Next, Args, This>} chain
88
139
  * The chain callback
89
- * @param {Reject<Next, any, Self>} [reject]
90
- * The callback to fire when a promise is rejected or an error is thrown
91
- * @param {Self | null | undefined} [context]
92
- * The `this` context of the chain and error callbacks
140
+ * @param {null | undefined} [fail]
141
+ * The callback to fire when a failure occurs
142
+ * @param {This | null | undefined} [context]
143
+ * The `this` context of the chain and `fail` callbacks
93
144
  * @param {Args} args
94
145
  * The arguments to pass to the chain callback
95
146
  * @return {Awaitable<Next>}
96
- * The next promise or value
147
+ * The next awaitable
97
148
  */
98
- declare function when<T, Next = any, Args extends any[] = any[], Self = unknown>(this: void, value: Awaitable<T>, chain: Chain<T, Next, Args, Self>, reject?: Reject<Next, any, Self> | null | undefined, context?: Self | null | undefined, ...args: Args): Awaitable<Next>;
149
+ declare 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>;
99
150
  /**
100
151
  * Chain a callback, calling the function after `value` is resolved,
101
- * or immediately if `value` is not thenable.
152
+ * or immediately if `value` is not a thenable.
153
+ *
154
+ * @see {@linkcode Awaitable}
155
+ * @see {@linkcode Chain}
156
+ * @see {@linkcode Fail}
157
+ *
158
+ * @template {any} T
159
+ * The previously resolved value
160
+ * @template {any} [Next=any]
161
+ * The next resolved value
162
+ * @template {any} [Failure=Next]
163
+ * The next resolved value on failure
164
+ * @template {ReadonlyArray<any>} [Args=any[]]
165
+ * The chain function arguments
166
+ * @template {any} [Error=any]
167
+ * The error to possibly handle
168
+ * @template {any} [This=any]
169
+ * The `this` context
170
+ *
171
+ * @this {void}
172
+ *
173
+ * @param {Awaitable<T>} value
174
+ * The current awaitable
175
+ * @param {Chain<T, Next, Args, This>} chain
176
+ * The chain callback
177
+ * @param {Fail<Failure, Error, This> | null | undefined} [fail]
178
+ * The callback to fire when a failure occurs
179
+ * @param {This | null | undefined} [context]
180
+ * The `this` context of the chain and `fail` callbacks
181
+ * @param {Args} args
182
+ * The arguments to pass to the chain callback
183
+ * @return {Awaitable<Failure | Next>}
184
+ * The next awaitable
185
+ */
186
+ declare 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>;
187
+ /**
188
+ * Chain a callback, calling the function after `value` is resolved,
189
+ * or immediately if `value` is not a thenable.
102
190
  *
103
191
  * @see {@linkcode Awaitable}
104
192
  * @see {@linkcode Options}
@@ -107,21 +195,25 @@ declare function when<T, Next = any, Args extends any[] = any[], Self = unknown>
107
195
  * The previously resolved value
108
196
  * @template {any} [Next=any]
109
197
  * The next resolved value
198
+ * @template {any} [Failure=Next]
199
+ * The next resolved value on failure
110
200
  * @template {ReadonlyArray<any>} [Args=any[]]
111
201
  * The chain function arguments
112
- * @template {any} [Self=unknown]
202
+ * @template {any} [Error=any]
203
+ * The error to possibly handle
204
+ * @template {any} [This=unknown]
113
205
  * The `this` context
114
206
  *
115
207
  * @this {void}
116
208
  *
117
209
  * @param {Awaitable<T>} value
118
- * The promise or the resolved value
119
- * @param {Options<T, Next, Args, Self>} chain
210
+ * The current awaitable
211
+ * @param {Options<T, Next, Failure, Args, Error, This>} chain
120
212
  * Options for chaining
121
- * @return {Awaitable<Next>}
122
- * The next promise or value
213
+ * @return {Awaitable<Failure | Next>}
214
+ * The next awaitable
123
215
  */
124
- declare function when<T, Next = any, Args extends any[] = any[], Self = unknown>(this: void, value: Awaitable<T>, chain: Options<T, Next, Args, Self>): Awaitable<Next>;
216
+ declare 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>;
125
217
 
126
218
  /**
127
219
  * @file Type Aliases - Awaitable
@@ -151,46 +243,45 @@ type Awaitable<T> = PromiseLike<T> | T;
151
243
  * The next resolved value
152
244
  * @template {ReadonlyArray<any>} [Args=any[]]
153
245
  * The function arguments
154
- * @template {any} [Self=unknown]
246
+ * @template {any} [This=unknown]
155
247
  * The `this` context
156
248
  *
157
- * @this {Self}
249
+ * @this {This}
158
250
  *
159
251
  * @param {[...Args, T]} params
160
252
  * The function parameters, with the last being the previously resolved value.\
161
253
  * In cases where a promise is not being resolved,
162
254
  * this is the same `value` passed to `when`
163
255
  * @return {Awaitable<Next>}
164
- * The next promise or value
256
+ * The next awaitable
165
257
  */
166
- type Chain<T = any, Next = any, Args extends readonly any[] = any[], Self = unknown> = (this: Self, ...params: [...Args, T]) => Awaitable<Next>;
258
+ type Chain<T = any, Next = any, Args extends readonly any[] = any[], This = unknown> = (this: This, ...params: [...Args, T]) => Awaitable<Next>;
167
259
 
168
260
  /**
169
- * @file Type Aliases - Reject
170
- * @module when/types/Reject
261
+ * @file Type Aliases - Fail
262
+ * @module when/types/Fail
171
263
  */
172
264
 
173
265
  /**
174
- * The callback to fire when a promise is rejected
175
- * or an error is thrown from a synchronous function.
266
+ * The callback to fire when a failure occurs.
176
267
  *
177
268
  * @see {@linkcode Awaitable}
178
269
  *
179
270
  * @template {any} [Next=any]
180
271
  * The next resolved value
181
- * @template {any} [Fail=any]
272
+ * @template {any} [Error=any]
182
273
  * The error to handle
183
- * @template {any} [Self=unknown]
274
+ * @template {any} [This=unknown]
184
275
  * The `this` context
185
276
  *
186
- * @this {Self}
277
+ * @this {This}
187
278
  *
188
279
  * @param {unknown} e
189
280
  * The error
190
281
  * @return {Awaitable<Next>}
191
- * The next promise or value
282
+ * The next awaitable
192
283
  */
193
- type Reject<Next = any, Fail = any, Self = unknown> = (this: Self, e: Fail) => Awaitable<Next>;
284
+ type Fail<Next = any, Error = any, This = unknown> = (this: This, e: Error) => Awaitable<Next>;
194
285
 
195
- export { when as default, isThenable as isPromiseLike, isThenable, when };
196
- export type { Awaitable, Chain, Options, Reject };
286
+ export { when as default, isPromise, isThenable as isPromiseLike, isThenable, when };
287
+ export type { Awaitable, Chain, Fail, Options };
@@ -2,5 +2,6 @@
2
2
  * @file Entry Point - Library
3
3
  * @module when/lib
4
4
  */
5
+ export { default as isPromise } from '#lib/is-promise';
5
6
  export { default as isPromiseLike, default as isThenable } from '#lib/is-thenable';
6
7
  export { default as when } from '#lib/when';
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @file isPromise
3
+ * @module when/lib/isPromise
4
+ */
5
+ import isThenable from '#lib/is-thenable';
6
+ /**
7
+ * Check if `value` looks like a {@linkcode Promise}.
8
+ *
9
+ * > 👉 **Note**: This function intentionally performs a structural check
10
+ * > instead of a brand check.
11
+ * > It does not rely on `instanceof Promise` or constructors, making it
12
+ * > compatible with cross-realm promises and custom thenables.
13
+ *
14
+ * @see {@linkcode isThenable}
15
+ *
16
+ * @template {any} T
17
+ * The resolved value
18
+ *
19
+ * @this {void}
20
+ *
21
+ * @param {unknown} value
22
+ * The thing to check
23
+ * @return {value is Promise<T>}
24
+ * `true` if `value` is a thenable with a `catch` method, `false` otherwise
25
+ */
26
+ function isPromise(value) {
27
+ if (!isThenable(value))
28
+ return false;
29
+ return 'catch' in value && typeof value.catch === 'function';
30
+ }
31
+ export default isPromise;
@@ -3,7 +3,8 @@
3
3
  * @module when/lib/isThenable
4
4
  */
5
5
  /**
6
- * Check if `value` looks like a thenable.
6
+ * Check if `value` looks like a thenable,
7
+ * i.e. a {@linkcode PromiseLike} object.
7
8
  *
8
9
  * @template {any} T
9
10
  * The resolved value
@@ -13,13 +14,14 @@
13
14
  * @param {unknown} value
14
15
  * The thing to check
15
16
  * @return {value is PromiseLike<T>}
16
- * `true` if `value` is a thenable, `false` otherwise
17
+ * `true` if `value` is an object or function with a `then` method,
18
+ * `false` otherwise
17
19
  */
18
20
  function isThenable(value) {
19
- return (!Array.isArray(value) &&
20
- typeof value === 'object' &&
21
- value !== null &&
22
- 'then' in value &&
23
- typeof value.then === 'function');
21
+ if (!value)
22
+ return false;
23
+ if (typeof value !== 'function' && typeof value !== 'object')
24
+ return false;
25
+ return 'then' in value && typeof value.then === 'function';
24
26
  }
25
27
  export default isThenable;
package/dist/lib/when.mjs CHANGED
@@ -2,34 +2,35 @@
2
2
  * @file when
3
3
  * @module when/lib/when
4
4
  */
5
+ import isPromise from '#lib/is-promise';
5
6
  import isThenable from '#lib/is-thenable';
6
7
  export default when;
7
8
  /**
8
9
  * Chain a callback, calling the function after `value` is resolved,
9
- * or immediately if `value` is not thenable.
10
+ * or immediately if `value` is not a thenable.
10
11
  *
11
12
  * @see {@linkcode Chain}
12
13
  * @see {@linkcode Options}
13
- * @see {@linkcode Reject}
14
+ * @see {@linkcode Fail}
14
15
  *
15
16
  * @this {void}
16
17
  *
17
18
  * @param {unknown} value
18
- * The promise or the resolved value
19
+ * The current awaitable
19
20
  * @param {Chain<any, unknown> | Options} chain
20
21
  * The chain callback or options for chaining
21
- * @param {Reject | null | undefined} [reject]
22
- * The callback to fire when a promise is rejected or an error is thrown
22
+ * @param {Fail | null | undefined} [fail]
23
+ * The callback to fire when a failure occurs
23
24
  * @param {unknown} [context]
24
- * The `this` context of the chain and error callbacks
25
+ * The `this` context of the chain and `fail` callbacks
25
26
  * @param {unknown[]} args
26
27
  * The arguments to pass to the chain callback
27
28
  * @return {unknown}
28
- * The next promise or value
29
+ * The next awaitable
29
30
  */
30
- function when(value, chain, reject, context, ...args) {
31
+ function when(value, chain, fail, context, ...args) {
31
32
  if (typeof chain === 'object') {
32
- reject = chain.reject;
33
+ fail = chain.fail;
33
34
  context = chain.context;
34
35
  args = chain.args ?? [];
35
36
  chain = chain.chain;
@@ -37,14 +38,17 @@ function when(value, chain, reject, context, ...args) {
37
38
  // no promise, call chain function immediately.
38
39
  if (!isThenable(value)) {
39
40
  try {
40
- return chain.call(context, ...args, value);
41
+ // try attaching "global" rejection handler with `catch`.
42
+ return katch(chain.call(context, ...args, value));
41
43
  }
42
44
  catch (e) {
43
- return fail(e);
45
+ return failure(e);
44
46
  }
45
47
  }
46
- // already have a promise, chain callback.
47
- return value.then(resolved => chain.call(context, ...args, resolved), fail);
48
+ // already have a promise, chain the chain callback.
49
+ value = value.then(res => chain.call(context, ...args, res), failure);
50
+ // try attaching "global" rejection handler with `catch`.
51
+ return katch(value);
48
52
  /**
49
53
  * @this {void}
50
54
  *
@@ -54,9 +58,24 @@ function when(value, chain, reject, context, ...args) {
54
58
  * The rejection result
55
59
  * @throws {unknown}
56
60
  */
57
- function fail(e) {
58
- if (typeof reject !== 'function')
61
+ function failure(e) {
62
+ if (typeof fail !== 'function')
59
63
  throw e;
60
- return reject.call(context, e);
64
+ return fail.call(context, e);
65
+ }
66
+ /**
67
+ * Try attaching a rejection handler with `catch`.
68
+ *
69
+ * @this {void}
70
+ *
71
+ * @param {unknown} value
72
+ * The awaitable
73
+ * @return {unknown}
74
+ * The `value`
75
+ */
76
+ function katch(value) {
77
+ if (isPromise(value))
78
+ value = value.catch(failure);
79
+ return value;
61
80
  }
62
81
  }
package/package.json CHANGED
@@ -1,19 +1,27 @@
1
1
  {
2
2
  "name": "@flex-development/when",
3
- "description": "Like .then, but for synchronous values and thenables",
4
- "version": "1.0.0",
3
+ "description": "Chain callbacks on synchronous values or thenables without forcing Promise.resolve or microtasks.",
4
+ "version": "2.0.0",
5
5
  "keywords": [
6
+ "async",
6
7
  "async",
7
8
  "await",
8
9
  "awaitable",
9
10
  "callback",
10
11
  "chain",
12
+ "esm",
13
+ "functional",
14
+ "hooks",
15
+ "microtask",
16
+ "pipeline",
11
17
  "promise",
18
+ "promise-like",
12
19
  "sync",
13
20
  "synchronous",
14
21
  "then",
15
22
  "thenable",
16
- "typescript"
23
+ "typescript",
24
+ "utility"
17
25
  ],
18
26
  "license": "BSD-3-Clause",
19
27
  "homepage": "https://github.com/flex-development/when",
@@ -114,7 +122,7 @@
114
122
  },
115
123
  "devDependencies": {
116
124
  "@arethetypeswrong/cli": "0.18.2",
117
- "@commitlint/cli": "20.4.1",
125
+ "@commitlint/cli": "20.4.2",
118
126
  "@commitlint/types": "20.4.0",
119
127
  "@edge-runtime/vm": "5.0.0",
120
128
  "@flex-development/commitlint-config": "1.0.1",
@@ -140,7 +148,7 @@
140
148
  "editorconfig": "3.0.1",
141
149
  "eslint": "9.39.2",
142
150
  "growl": "1.10.5",
143
- "happy-dom": "20.6.2",
151
+ "happy-dom": "20.6.3",
144
152
  "husky": "9.1.7",
145
153
  "is-ci": "4.1.0",
146
154
  "node-notifier": "10.0.1",