Skip to content

Commit 0bdbc47

Browse files
committed
feat: when
Signed-off-by: Lexus Drumgold <unicornware@flexdevelopment.llc>
1 parent a490ffe commit 0bdbc47

17 files changed

Lines changed: 817 additions & 6 deletions

.dictionary.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ jchen
1010
kaisugi
1111
nvmrc
1212
shfmt
13+
succ
1314
unstub
1415
vates
1516
vitest

.github/infrastructure.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ repository:
178178
automated_security_fixes: true
179179
default_branch: main
180180
delete_branch_on_merge: true
181-
description: ⛓️ chain a callback onto a value or promise
181+
description: .then for values and thenables
182182
has_issues: true
183183
has_projects: true
184184
has_wiki: false
@@ -192,6 +192,8 @@ repository:
192192
- awaitable
193193
- chain
194194
- promise
195+
- then
196+
- thenable
195197
visibility: public
196198
vulnerability_alerts: true
197199
web_commit_signoff_required: true

README.md

Lines changed: 202 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,21 @@
1212
[![vitest](https://img.shields.io/badge/-vitest-6e9f18?style=flat\&logo=vitest\&logoColor=ffffff)](https://vitest.dev)
1313
[![yarn](https://img.shields.io/badge/-yarn-2c8ebb?style=flat\&logo=yarn\&logoColor=ffffff)](https://yarnpkg.com)
1414

15-
chain a callback onto a value or promise
15+
like `.then`, but for values *and* thenables.
1616

1717
## Contents
1818

1919
- [What is this?](#what-is-this)
2020
- [Install](#install)
2121
- [Use](#use)
2222
- [API](#api)
23+
- [`isPromiseLike<T>(value)`][ispromiselike]
24+
- [`when<[T][, Next][, Args][, Self]>(value, chain[, reject][, context][, ...args])`][when]
2325
- [Types](#types)
24-
- [`Awaitable<T>`](#awaitablet)
26+
- [`Awaitable<T>`][awaitable]
27+
- [`Chain<[T][, Next][, Args][, Self]>`][chain]
28+
- [`Options<[T][, Next][, Args][, Self]>`][options]
29+
- [`Reject<[Next][, Fail][, Self]>`][reject]
2530
- [Project](#project)
2631
- [Version](#version)
2732
- [Contribute](#contribute)
@@ -69,7 +74,7 @@ In browsers with [`esm.sh`][esmsh]:
6974

7075
`when` exports the identifiers listed below.
7176

72-
The default export is `when`.
77+
The default export is [`when`][when].
7378

7479
### `isPromiseLike<T>(value)`
7580

@@ -89,6 +94,76 @@ Check if `value` looks like a promise.
8994

9095
(`value is PromiseLike<T>`) `true` if `value` is `PromiseLike` object, `false` otherwise
9196

97+
<!--lint disable-->
98+
99+
### `when<[T][, Next][, Args][, Self]>(value, chain[, reject][, context][, ...args])`
100+
101+
<!--lint enable-->
102+
103+
Chain a callback, calling the function after `value` is resolved, or immediately if `value` is not a promise.
104+
105+
#### Overloads
106+
107+
```ts
108+
function when<
109+
T,
110+
Next = any,
111+
Args extends any[] = any[],
112+
Self = unknown
113+
>(
114+
this: void,
115+
value: Awaitable<T>,
116+
chain: Chain<T, Next, Args, Self>,
117+
reject?: Reject<Next, any, Self> | null | undefined,
118+
context?: Self | null | undefined,
119+
...args: Args
120+
): Awaitable<Next>
121+
```
122+
123+
```ts
124+
function when<
125+
T,
126+
Next = any,
127+
Args extends any[] = any[],
128+
Self = unknown
129+
>(
130+
this: void,
131+
value: Awaitable<T>,
132+
chain: Options<T, Next, Args, Self>
133+
): Awaitable<Next>
134+
```
135+
136+
#### Type Parameters
137+
138+
- `T` (`any`)
139+
the previously resolved value
140+
- `Next` (`any`, optional)
141+
the next resolved value
142+
- **default**: `any`
143+
- `Args` (`readonly any[]`, optional)
144+
the function arguments
145+
- **default**: `any[]`
146+
- `Self` (`any`, optional)
147+
— the `this` context
148+
- **default**: `unknown`
149+
150+
#### Parameters
151+
152+
- `value` ([`Awaitable<T>`][awaitable])
153+
the promise or the resolved value
154+
- `chain` ([`Chain<T, Next, Args, Self>`][chain] | [`Options<T, Next, Args, Self>`][options])
155+
the chain callback or options for chaining
156+
- `reject` ([`Reject<Next, any, Self>`][reject] | `null` | `undefined`)
157+
the callback to fire when a promise is rejected or an error is thrown
158+
- `context` (`Self` | `null` | `undefined`)
159+
the `this` context of the chain and error callbacks
160+
- `...args` (`Args`)
161+
the arguments to pass to the chain callback
162+
163+
#### Returns
164+
165+
([`Awaitable<Next>`][awaitable]) The next promise or value
166+
92167
## Types
93168

94169
This package is fully typed with [TypeScript][].
@@ -106,6 +181,118 @@ type Awaitable<T> = PromiseLike<T> | T
106181
- `T` (`any`)
107182
the value
108183

184+
### `Chain<[T][, Next][, Args][, Self]>`
185+
186+
A chain callback (`type`).
187+
188+
```ts
189+
type Chain<
190+
T = any,
191+
Next = any,
192+
Args extends readonly any[] = any[],
193+
Self = unknown
194+
> = (this: Self, ...params: [...Args, T]) => Awaitable<Next>
195+
```
196+
197+
#### Type Parameters
198+
199+
- `T` (`any`, optional)
200+
the previously resolved value
201+
- **default**: `any`
202+
- `Next` (`any`, optional)
203+
the next resolved value
204+
- **default**: `any`
205+
- `Args` (`readonly any[]`, optional)
206+
the function arguments
207+
- **default**: `any[]`
208+
- `Self` (`any`, optional)
209+
— the `this` context
210+
- **default**: `unknown`
211+
212+
#### Parameters
213+
214+
- **`this`** (`Self`)
215+
- `...params` (`[...Args, T]`)
216+
the function parameters, with the last being the previously resolved value.
217+
in cases where a promise is not being resolved, this is the same `value` passed to `when`
218+
219+
#### Returns
220+
221+
([`Awaitable<Next>`][awaitable]) The next promise or value
222+
223+
### `Options<[T][, Next][, Args][, Self]>`
224+
225+
Options for chaining (`interface`).
226+
227+
```ts
228+
interface Options<
229+
T = any,
230+
Next = any,
231+
Args extends readonly any[] = any[],
232+
Self = any
233+
> { /* ... */ }
234+
```
235+
236+
#### Type Parameters
237+
238+
- `T` (`any`, optional)
239+
the previously resolved value
240+
- **default**: `any`
241+
- `Next` (`any`, optional)
242+
the next resolved value
243+
- **default**: `any`
244+
- `Args` (`readonly any[]`, optional)
245+
the chain function arguments
246+
- **default**: `any[]`
247+
- `Self` (`any`, optional)
248+
— the `this` context
249+
- **default**: `any`
250+
251+
#### Properties
252+
253+
- `args?` (`Args` | `null` | `undefined`)
254+
the arguments to pass to the `chain` callback
255+
- `chain` ([`Chain<T, Next, Args, Self>`][chain])
256+
the chain callback
257+
- `context?` (`Self` | `null` | `undefined`)
258+
the `this` context of the `chain` and `reject` callbacks
259+
- `reject?` ([`Reject<Next, any, Self>`][reject] | `null` | `undefined`)
260+
the callback to fire when a promise is rejected or an error is thrown
261+
262+
### `Reject<[Next][, Fail][, Self]>`
263+
264+
The callback to fire when a promise is rejected or an error is thrown from a synchronous function (`type`).
265+
266+
```ts
267+
type Reject<
268+
Next = any,
269+
Fail = any,
270+
Self = unknown
271+
> = (this: Self, e: Fail) => Awaitable<Next>
272+
```
273+
274+
#### Type Parameters
275+
276+
- `Next` (`any`, optional)
277+
the next resolved value
278+
- **default**: `any`
279+
- `Fail` (`any`, optional)
280+
the error to handle
281+
- **default**: `any`
282+
- `Self` (`any`, optional)
283+
the `this` context
284+
- **default**: `unknown`
285+
286+
#### Parameters
287+
288+
- **`this`** (`Self`)
289+
- `e` (`Fail`)
290+
the error
291+
292+
#### Returns
293+
294+
([`Awaitable<Next>`][awaitable]) The next promise or value
295+
109296
## Project
110297

111298
### Version
@@ -119,12 +306,24 @@ See [`CONTRIBUTING.md`](CONTRIBUTING.md).
119306
This project has a [code of conduct](./CODE_OF_CONDUCT.md).
120307
By interacting with this repository, organization, or community you agree to abide by its terms.
121308

309+
[awaitable]: #awaitablet
310+
311+
[chain]: #chaint-next-args-self
312+
122313
[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
123314

124315
[esmsh]: https://esm.sh
125316

317+
[ispromiselike]: #ispromiseliketvalue
318+
319+
[options]: #optionst-next-args-self
320+
321+
[reject]: #rejectnext-fail-self
322+
126323
[semver]: https://semver.org
127324

128325
[typescript]: https://www.typescriptlang.org
129326

327+
[when]: #whent-next-args-selfvalue-chain-reject-context-args
328+
130329
[yarn]: https://yarnpkg.com

eslint.config.mjs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@ const config = [
1919
rules: {
2020
'unicorn/no-thenable': 0
2121
}
22+
},
23+
{
24+
files: ['src/lib/when.mts'],
25+
rules: {
26+
'promise/prefer-await-to-then': 0
27+
}
28+
},
29+
{
30+
files: ['src/types/chain.mts'],
31+
rules: {
32+
'jsdoc/valid-types': 0
33+
}
2234
}
2335
]
2436

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@flex-development/when",
3-
"description": "Chain a callback onto a value or promise",
3+
"description": "Like .then, but for values and thenables",
44
"version": "0.0.0",
55
"keywords": [
66
"async",
@@ -10,6 +10,8 @@
1010
"chain",
1111
"promise",
1212
"sync",
13+
"then",
14+
"thenable",
1315
"typescript"
1416
],
1517
"license": "BSD-3-Clause",
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* @file Type Tests - Options
3+
* @module when/types/tests/unit-d/Options
4+
*/
5+
6+
import type TestSubject from '#interfaces/options'
7+
import type { Nilable } from '@flex-development/tutils'
8+
import type { Chain, Reject } from '@flex-development/when'
9+
10+
describe('unit-d:interfaces/Options', () => {
11+
type T = URL | string
12+
type Next = string
13+
type Args = [T]
14+
type Self = undefined
15+
type Subject = TestSubject<T, Next, Args, Self>
16+
17+
it('should match [args?: Args | null | undefined]', () => {
18+
expectTypeOf<Subject>()
19+
.toHaveProperty('args')
20+
.toEqualTypeOf<Nilable<Args>>()
21+
})
22+
23+
it('should match [chain: Chain<T, Next, Args, Self>]', () => {
24+
expectTypeOf<Subject>()
25+
.toHaveProperty('chain')
26+
.toEqualTypeOf<Chain<T, Next, Args, Self>>()
27+
})
28+
29+
it('should match [context?: Self | null | undefined]', () => {
30+
expectTypeOf<Subject>()
31+
.toHaveProperty('context')
32+
.toEqualTypeOf<Nilable<Self>>()
33+
})
34+
35+
it('should match [reject?: Reject<Next, any, Self> | null | undefined]', () => {
36+
expectTypeOf<Subject>()
37+
.toHaveProperty('reject')
38+
.toEqualTypeOf<Nilable<Reject<Next, any, Self>>>()
39+
})
40+
})

src/interfaces/index.mts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
* @module when/interfaces
44
*/
55

6-
export type {}
6+
export type { default as Options } from '#interfaces/options'

0 commit comments

Comments
 (0)