1
1
// Promises/A+ spec
2
2
// https://github.com/promises-aplus/promises-spec
3
-
4
- import isFunction from 'lodash.isfunction' ;
5
3
import { ValueOf } from 'type-fest' ;
6
4
7
5
/**
@@ -48,21 +46,19 @@ interface IMyPromise<T> {
48
46
readonly [ Symbol . toStringTag ] : string ;
49
47
}
50
48
51
- const STATE = {
49
+ const STATES = {
52
50
PENDING : 'pending' ,
53
51
FULFILLED : 'fulfilled' ,
54
52
REJECTED : 'rejected' ,
55
53
} as const ;
56
54
57
- type Callback = ( ) => void ;
58
-
59
55
interface PromiseFulfilledResult < T > {
60
- status : typeof STATE . FULFILLED ;
56
+ status : typeof STATES . FULFILLED ;
61
57
value : T ;
62
58
}
63
59
64
60
interface PromiseRejectedResult {
65
- status : typeof STATE . REJECTED ;
61
+ status : typeof STATES . REJECTED ;
66
62
reason : any ;
67
63
}
68
64
@@ -71,16 +67,17 @@ type PromiseSettledResult<T> =
71
67
| PromiseRejectedResult ;
72
68
73
69
export class MyPromise < T = any > implements IMyPromise < T > {
74
- #state: ValueOf < typeof STATE > = STATE . PENDING ;
70
+ #state: ValueOf < typeof STATES > = STATES . PENDING ;
75
71
76
- #value? : T | PromiseLike < T > ;
72
+ #value: T | PromiseLike < T > | undefined = undefined ;
77
73
78
- #reason: any ;
74
+ #reason: any = undefined ;
79
75
80
- #onfulfilledCallbacks: Callback [ ] = [ ] ;
76
+ #fulfilledCallbacksQueue: ( ( ) => void ) [ ] = [ ] ;
81
77
82
- #onrejectedCallbacks: Callback [ ] = [ ] ;
78
+ #rejectedCallbacksQueue: ( ( ) => void ) [ ] = [ ] ;
83
79
80
+ // --- Resolve --------------------
84
81
static resolve ( ) : MyPromise < void > ;
85
82
static resolve < T > ( value : T ) : MyPromise < Awaited < T > > ;
86
83
static resolve < T > ( value : T | PromiseLike < T > ) : MyPromise < Awaited < T > > ;
@@ -92,11 +89,13 @@ export class MyPromise<T = any> implements IMyPromise<T> {
92
89
return new MyPromise < Awaited < T > > ( ( resolve ) => resolve ( value as Awaited < T > ) ) ;
93
90
}
94
91
92
+ // --- Reject --------------------
95
93
static reject < T = never > ( reason ?: any ) {
96
94
return new MyPromise < T > ( ( _ , reject ) => reject ( reason ) ) ;
97
95
}
98
96
99
- static all < T > ( values : Iterable < T | PromiseLike < T > > ) {
97
+ // --- All --------------------
98
+ static all < T > ( values : Iterable < T | PromiseLike < T > > = [ ] ) {
100
99
return new MyPromise < Awaited < T > [ ] > ( ( resolve , reject ) => {
101
100
handleNonIterable ( values ) ;
102
101
@@ -127,6 +126,7 @@ export class MyPromise<T = any> implements IMyPromise<T> {
127
126
} ) ;
128
127
}
129
128
129
+ // --- Race --------------------
130
130
static race < T > ( values : Iterable < T | PromiseLike < T > > = [ ] ) {
131
131
return new MyPromise < Awaited < T > > ( ( resolve , reject ) => {
132
132
handleNonIterable ( values ) ;
@@ -137,11 +137,12 @@ export class MyPromise<T = any> implements IMyPromise<T> {
137
137
} ) ;
138
138
}
139
139
140
+ // --- Any --------------------
140
141
static any < T extends readonly unknown [ ] | [ ] > (
141
142
values : T ,
142
143
) : MyPromise < Awaited < T [ number ] > > ;
143
144
static any < T > ( values : Iterable < T | PromiseLike < T > > ) : MyPromise < Awaited < T > > ;
144
- static any < T > ( values : Iterable < T | PromiseLike < T > > ) {
145
+ static any < T > ( values : Iterable < T | PromiseLike < T > > = [ ] ) {
145
146
return new MyPromise < Awaited < T > > ( ( resolve , reject ) => {
146
147
handleNonIterable ( values ) ;
147
148
@@ -173,6 +174,7 @@ export class MyPromise<T = any> implements IMyPromise<T> {
173
174
} ) ;
174
175
}
175
176
177
+ // --- All Settled --------------------
176
178
static allSettled < T extends readonly unknown [ ] | [ ] > (
177
179
values : T ,
178
180
) : MyPromise < {
@@ -185,11 +187,11 @@ export class MyPromise<T = any> implements IMyPromise<T> {
185
187
const items = Array . from ( values ) . map ( ( item ) =>
186
188
MyPromise . resolve ( item ) . then (
187
189
( value ) => ( {
188
- status : STATE . FULFILLED ,
190
+ status : STATES . FULFILLED ,
189
191
value,
190
192
} ) ,
191
193
( reason ) => ( {
192
- status : STATE . REJECTED ,
194
+ status : STATES . REJECTED ,
193
195
reason,
194
196
} ) ,
195
197
) ,
@@ -198,6 +200,7 @@ export class MyPromise<T = any> implements IMyPromise<T> {
198
200
return MyPromise . all < PromiseSettledResult < Awaited < T > > > ( items ) ;
199
201
}
200
202
203
+ // --- With Resolvers --------------------
201
204
static withResolvers < T > ( ) {
202
205
let resolve ! : ( value : T | PromiseLike < T > ) => void ;
203
206
let reject ! : ( reason ?: any ) => void ;
@@ -214,114 +217,135 @@ export class MyPromise<T = any> implements IMyPromise<T> {
214
217
} ;
215
218
}
216
219
220
+ // --- Constructor --------------------
217
221
constructor (
218
222
executor : (
219
223
resolve : ( value : T | PromiseLike < T > ) => void ,
220
224
reject : ( reason ?: any ) => void ,
221
225
) => void ,
222
226
) {
227
+ // Error Handling -----
223
228
if ( new . target === undefined ) {
224
229
throw new TypeError (
225
230
`${ this . constructor . name } constructor cannot be invoked without 'new'` ,
226
231
) ;
227
232
}
228
233
229
- if ( typeof executor !== 'function' ) {
234
+ if ( ! isFunction ( executor ) ) {
230
235
throw new TypeError (
231
- `${ this . constructor . name } resolver ${ typeof executor } is not a function` ,
236
+ `${ this . constructor . name } resolver ${ type ( executor ) } is not a function` ,
232
237
) ;
233
238
}
234
239
235
- try {
236
- executor ( this . #resolve. bind ( this ) , this . #reject. bind ( this ) ) ;
237
- } catch ( error ) {
238
- this . #reject( error ) ;
239
- }
240
- }
240
+ // Resolve -----
241
+ const internalResolve = ( value : T | PromiseLike < T > ) => {
242
+ if ( this . #state !== STATES . PENDING ) return ;
241
243
242
- #resolve ( value : T | PromiseLike < T > ) {
243
- if ( this . #state !== STATE . PENDING ) return ;
244
+ this . #state = STATES . FULFILLED ;
245
+ this . #value = value ;
244
246
245
- this . #state = STATE . FULFILLED ;
246
- this . #value = value ;
247
+ this . #fulfilledCallbacksQueue. forEach ( ( callback ) => {
248
+ callback ( ) ;
249
+ } ) ;
247
250
248
- executeCallbacks ( this . #onfulfilledCallbacks) ;
249
- this . #onfulfilledCallbacks = [ ] ;
250
- }
251
+ this . #fulfilledCallbacksQueue = [ ] ;
252
+ } ;
251
253
252
- #reject( reason ?: any ) {
253
- if ( this . #state !== STATE . PENDING ) return ;
254
+ // Reject -----
255
+ const internalReject = ( reason : any ) => {
256
+ if ( this . #state !== STATES . PENDING ) return ;
254
257
255
- this . #state = STATE . REJECTED ;
256
- this . #reason = reason ;
258
+ this . #state = STATES . REJECTED ;
259
+ this . #reason = reason ;
257
260
258
- executeCallbacks ( this . #onrejectedCallbacks) ;
259
- this . #onfulfilledCallbacks = [ ] ;
261
+ this . #rejectedCallbacksQueue. forEach ( ( callback ) => {
262
+ callback ( ) ;
263
+ } ) ;
264
+
265
+ this . #fulfilledCallbacksQueue = [ ] ;
266
+ } ;
267
+
268
+ // Constructor execution -----
269
+ try {
270
+ executor ( internalResolve , internalReject ) ;
271
+ } catch ( error ) {
272
+ queueMicrotask ( ( ) => {
273
+ internalReject ( error ) ;
274
+ } ) ;
275
+ }
260
276
}
261
277
278
+ // --- Then --------------------
262
279
then < TResult1 = T , TResult2 = never > (
263
280
onfulfilled ?: ( ( value : T ) => TResult1 | PromiseLike < TResult1 > ) | null ,
264
281
onrejected ?: ( ( reason : any ) => TResult2 | PromiseLike < TResult2 > ) | null ,
265
282
) {
266
283
return new MyPromise ( ( resolve , reject ) => {
267
- const handleFulfilled = executeHandler ( onfulfilled , resolve ) ;
268
- const handleRejected = executeHandler ( onrejected , reject ) ;
284
+ if ( this . #state === STATES . PENDING ) {
285
+ this . #fulfilledCallbacksQueue. push ( ( ) => {
286
+ handleFulfilled ( this . #value as T ) ;
287
+ } ) ;
288
+
289
+ this . #rejectedCallbacksQueue. push ( ( ) => {
290
+ handleRejected ( this . #reason) ;
291
+ } ) ;
292
+ } else if ( this . #state === STATES . FULFILLED ) {
293
+ if ( isThenable ( this . #value) ) {
294
+ this . #value. then ( handleFulfilled , handleFulfilled ) ;
295
+ } else {
296
+ handleFulfilled ( this . #value as T ) ;
297
+ }
298
+ } else {
299
+ handleRejected ( this . #reason) ;
300
+ }
269
301
270
- const strategy = {
271
- [ STATE . FULFILLED ] : ( ) => {
302
+ function handleFulfilled ( value : T ) {
303
+ if ( isFunction ( onfulfilled ) ) {
272
304
queueMicrotask ( ( ) => {
273
- if ( isThenable ( this . #value) ) {
274
- this . #value. then ( handleFulfilled ) ;
275
- } else {
276
- handleFulfilled ( this . #value) ;
277
- }
305
+ executeCallback ( onfulfilled , value ) ;
278
306
} ) ;
279
- } ,
280
- [ STATE . REJECTED ] : ( ) => {
307
+ } else {
308
+ resolve ( value ) ;
309
+ }
310
+ }
311
+
312
+ function handleRejected ( reason : any ) {
313
+ if ( isFunction ( onrejected ) ) {
281
314
queueMicrotask ( ( ) => {
282
- handleRejected ( this . # reason) ;
315
+ executeCallback ( onrejected , reason ) ;
283
316
} ) ;
284
- } ,
285
- [ STATE . PENDING ] : ( ) => {
286
- this . #onfulfilledCallbacks. push ( ( ) => handleFulfilled ( this . #value) ) ;
287
- this . #onrejectedCallbacks. push ( ( ) => handleRejected ( this . #reason) ) ;
288
- } ,
289
- } ;
290
-
291
- strategy [ this . #state] ( ) ;
292
-
293
- function executeHandler (
294
- handler : typeof onfulfilled | typeof onrejected ,
295
- resolver : ( value : any ) => void ,
317
+ } else {
318
+ reject ( reason ) ;
319
+ }
320
+ }
321
+
322
+ function executeCallback (
323
+ callback : typeof onfulfilled | typeof onrejected ,
324
+ data : any ,
296
325
) {
297
- return ( value : any ) => {
298
- try {
299
- if ( ! isFunction ( handler ) ) {
300
- resolver ( value ) ;
301
-
302
- return ;
303
- }
304
-
305
- const result = handler ( value ) ;
306
- if ( isThenable ( result ) ) {
307
- result . then ( resolve , reject ) ;
308
- } else {
309
- resolve ( result ) ;
310
- }
311
- } catch ( error ) {
312
- reject ( error ) ;
326
+ try {
327
+ const result = callback && callback ( data ) ;
328
+
329
+ if ( isThenable ( result ) ) {
330
+ result . then ( resolve , reject ) ;
331
+ } else {
332
+ resolve ( result ) ;
313
333
}
314
- } ;
334
+ } catch ( error ) {
335
+ reject ( error ) ;
336
+ }
315
337
}
316
338
} ) ;
317
339
}
318
340
341
+ // --- Catch --------------------
319
342
catch < TResult = never > (
320
343
onrejected ?: ( ( reason : any ) => TResult | PromiseLike < TResult > ) | null ,
321
344
) {
322
345
return this . then ( null , onrejected ) ;
323
346
}
324
347
348
+ // --- Finally --------------------
325
349
finally ( onfinally ?: ( ( ) => void ) | undefined | null ) {
326
350
return this . then (
327
351
( value ) => {
@@ -346,13 +370,13 @@ export class MyPromise<T = any> implements IMyPromise<T> {
346
370
}
347
371
}
348
372
373
+ // --- Utils --------------------
349
374
function handleNonIterable ( it : any ) : void {
350
375
if ( it [ Symbol . iterator ] ) return ;
351
376
352
- let type = typeof it ;
353
- let prefix = `${ type } ` ;
377
+ let prefix = `${ type ( it ) } ` ;
354
378
355
- if ( type === 'number' || type === 'boolean' ) {
379
+ if ( type ( it ) === 'number' || type ( it ) === 'boolean' ) {
356
380
prefix += ` ${ it } ` ;
357
381
}
358
382
@@ -361,14 +385,18 @@ function handleNonIterable(it: any): void {
361
385
) ;
362
386
}
363
387
364
- function isThenable < T > ( it : any ) : it is PromiseLike < T > {
365
- return ! ! ( it && isFunction ( it . then ) ) ;
388
+ function isThenable ( it : any ) : it is PromiseLike < unknown > {
389
+ return Boolean ( isObject ( it ) && isFunction ( it . then ) ) ;
366
390
}
367
391
368
- function executeCallbacks ( queue : Callback [ ] ) {
369
- for ( const callback of queue ) {
370
- queueMicrotask ( ( ) => {
371
- callback ( ) ;
372
- } ) ;
373
- }
392
+ function isObject ( it : unknown ) {
393
+ return type ( it ) === 'object' ? it !== null : isFunction ( it ) ;
394
+ }
395
+
396
+ function isFunction ( it : unknown ) : it is ( ...args : unknown [ ] ) => unknown {
397
+ return type ( it ) === 'function' ;
398
+ }
399
+
400
+ function type ( it : unknown ) {
401
+ return typeof it ;
374
402
}
0 commit comments