@@ -9,111 +9,113 @@ export const enum PromiseState {
99 Rejected ,
1010}
1111
12- type FulfillCallback < TData , TResult > = ( value : TData ) => TResult | PromiseLike < TResult > ;
13- type RejectCallback < TResult > = ( reason : any ) => TResult | PromiseLike < TResult > ;
14-
15- function promiseDeferred < T > ( ) {
16- let resolve : FulfillCallback < T , unknown > ;
17- let reject : RejectCallback < unknown > ;
18- const promise = new Promise < T > ( ( res , rej ) => {
12+ type PromiseExecutor < T > = ConstructorParameters < typeof Promise < T > > [ 0 ] ;
13+ type PromiseResolve < T > = Parameters < PromiseExecutor < T > > [ 0 ] ;
14+ type PromiseReject = Parameters < PromiseExecutor < unknown > > [ 1 ] ;
15+ type PromiseResolveCallback < TValue , TResult > = ( value : TValue ) => TResult | PromiseLike < TResult > ;
16+ type PromiseRejectCallback < TResult > = ( reason : any ) => TResult | PromiseLike < TResult > ;
17+
18+ function makeDeferredPromiseFactory ( this : void ) {
19+ let resolve : PromiseResolve < any > ;
20+ let reject : PromiseReject ;
21+ const executor : PromiseExecutor < any > = ( res , rej ) => {
1922 resolve = res ;
2023 reject = rej ;
21- } ) ;
22-
23- // @ts -ignore This is alright because TS doesnt understand the callback will immediately be called
24- return { promise, resolve, reject } ;
24+ } ;
25+ return function < T > ( this : void ) {
26+ const promise = new Promise < T > ( executor ) ;
27+ return $multi ( promise , resolve , reject ) ;
28+ } ;
2529}
2630
27- function isPromiseLike < T > ( thing : unknown ) : thing is PromiseLike < T > {
28- return thing instanceof __TS__Promise ;
31+ const makeDeferredPromise = makeDeferredPromiseFactory ( ) ;
32+
33+ function isPromiseLike < T > ( this : void , value : unknown ) : value is PromiseLike < T > {
34+ return value instanceof __TS__Promise ;
2935}
3036
37+ function doNothing ( ) : void { }
38+
39+ const pcall = _G . pcall ;
40+
3141export class __TS__Promise < T > implements Promise < T > {
3242 public state = PromiseState . Pending ;
3343 public value ?: T ;
3444 public rejectionReason ?: any ;
3545
36- private fulfilledCallbacks : Array < FulfillCallback < T , unknown > > = [ ] ;
37- private rejectedCallbacks : Array < RejectCallback < unknown > > = [ ] ;
46+ private fulfilledCallbacks : Array < PromiseResolve < T > > = [ ] ;
47+ private rejectedCallbacks : PromiseReject [ ] = [ ] ;
3848 private finallyCallbacks : Array < ( ) => void > = [ ] ;
3949
4050 // @ts -ignore
4151 public [ Symbol . toStringTag ] : string ; // Required to implement interface, no output Lua
4252
4353 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
44- public static resolve < TData > ( this : void , data : TData ) : Promise < TData > {
54+ public static resolve < T > ( this : void , value : T | PromiseLike < T > ) : __TS__Promise < Awaited < T > > {
55+ if ( value instanceof __TS__Promise ) {
56+ return value ;
57+ }
4558 // Create and return a promise instance that is already resolved
46- const promise = new __TS__Promise < TData > ( ( ) => { } ) ;
59+ const promise = new __TS__Promise < Awaited < T > > ( doNothing ) ;
4760 promise . state = PromiseState . Fulfilled ;
48- promise . value = data ;
61+ promise . value = value as Awaited < T > ;
4962 return promise ;
5063 }
5164
5265 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject
53- public static reject ( this : void , reason : any ) : Promise < never > {
66+ public static reject < T = never > ( this : void , reason ? : any ) : __TS__Promise < T > {
5467 // Create and return a promise instance that is already rejected
55- const promise = new __TS__Promise < never > ( ( ) => { } ) ;
68+ const promise = new __TS__Promise < T > ( doNothing ) ;
5669 promise . state = PromiseState . Rejected ;
5770 promise . rejectionReason = reason ;
5871 return promise ;
5972 }
6073
61- constructor ( executor : ( resolve : ( data : T ) => void , reject : ( reason : any ) => void ) => void ) {
62- try {
63- executor ( this . resolve . bind ( this ) , this . reject . bind ( this ) ) ;
64- } catch ( e ) {
74+ constructor ( executor : PromiseExecutor < T > ) {
75+ // Avoid unnecessary local functions allocations by using `pcall` explicitly
76+ const [ success , error ] = pcall (
77+ executor ,
78+ undefined ,
79+ v => this . resolve ( v ) ,
80+ err => this . reject ( err )
81+ ) ;
82+ if ( ! success ) {
6583 // When a promise executor throws, the promise should be rejected with the thrown object as reason
66- this . reject ( e ) ;
84+ this . reject ( error ) ;
6785 }
6886 }
6987
7088 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
7189 public then < TResult1 = T , TResult2 = never > (
72- onFulfilled ?: FulfillCallback < T , TResult1 > ,
73- onRejected ?: RejectCallback < TResult2 >
90+ onFulfilled ?: PromiseResolveCallback < T , TResult1 > ,
91+ onRejected ?: PromiseRejectCallback < TResult2 >
7492 ) : Promise < TResult1 | TResult2 > {
75- const { promise, resolve, reject } = promiseDeferred < T | TResult1 | TResult2 > ( ) ;
76-
77- const isFulfilled = this . state === PromiseState . Fulfilled ;
78- const isRejected = this . state === PromiseState . Rejected ;
79-
80- if ( onFulfilled ) {
81- const internalCallback = this . createPromiseResolvingCallback ( onFulfilled , resolve , reject ) ;
82- this . fulfilledCallbacks . push ( internalCallback ) ;
93+ const [ promise , resolve , reject ] = makeDeferredPromise < T | TResult1 | TResult2 > ( ) ;
8394
84- if ( isFulfilled ) {
85- // If promise already resolved, immediately call callback
86- internalCallback ( this . value ! ) ;
87- }
88- } else {
95+ this . addCallbacks (
8996 // We always want to resolve our child promise if this promise is resolved, even if we have no handler
90- this . fulfilledCallbacks . push ( v => resolve ( v ) ) ;
91- }
92-
93- if ( onRejected ) {
94- const internalCallback = this . createPromiseResolvingCallback ( onRejected , resolve , reject ) ;
95- this . rejectedCallbacks . push ( internalCallback ) ;
96-
97- if ( isRejected ) {
98- // If promise already rejected, immediately call callback
99- internalCallback ( this . rejectionReason ) ;
100- }
101- } else {
97+ onFulfilled ? this . createPromiseResolvingCallback ( onFulfilled , resolve , reject ) : resolve ,
10298 // We always want to reject our child promise if this promise is rejected, even if we have no handler
103- this . rejectedCallbacks . push ( err => reject ( err ) ) ;
104- }
99+ onRejected ? this . createPromiseResolvingCallback ( onRejected , resolve , reject ) : reject
100+ ) ;
105101
106- if ( isFulfilled ) {
107- // If promise already resolved, also resolve returned promise
108- resolve ( this . value ! ) ;
109- }
102+ return promise as Promise < TResult1 | TResult2 > ;
103+ }
110104
111- if ( isRejected ) {
112- // If promise already rejected, also reject returned promise
113- reject ( this . rejectionReason ) ;
105+ // Both callbacks should never throw!
106+ public addCallbacks ( fulfilledCallback : ( value : T ) => void , rejectedCallback : ( rejectionReason : any ) => void ) : void {
107+ if ( this . state === PromiseState . Fulfilled ) {
108+ // If promise already resolved, immediately call callback. We don't even need to store rejected callback
109+ // Tail call return is important!
110+ return fulfilledCallback ( this . value ! ) ;
111+ }
112+ if ( this . state === PromiseState . Rejected ) {
113+ // Similar thing
114+ return rejectedCallback ( this . rejectionReason ) ;
114115 }
115116
116- return promise as Promise < TResult1 | TResult2 > ;
117+ this . fulfilledCallbacks . push ( fulfilledCallback as any ) ;
118+ this . rejectedCallbacks . push ( rejectedCallback ) ;
117119 }
118120
119121 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
@@ -134,26 +136,22 @@ export class __TS__Promise<T> implements Promise<T> {
134136 return this ;
135137 }
136138
137- private resolve ( data : T ) : void {
138- if ( data instanceof __TS__Promise ) {
139- data . then (
139+ private resolve ( value : T | PromiseLike < T > ) : void {
140+ if ( isPromiseLike ( value ) ) {
141+ // Tail call return is important!
142+ return ( value as __TS__Promise < T > ) . addCallbacks (
140143 v => this . resolve ( v ) ,
141144 err => this . reject ( err )
142145 ) ;
143- return ;
144146 }
145147
146148 // Resolve this promise, if it is still pending. This function is passed to the constructor function.
147149 if ( this . state === PromiseState . Pending ) {
148150 this . state = PromiseState . Fulfilled ;
149- this . value = data ;
151+ this . value = value ;
150152
151- for ( const callback of this . fulfilledCallbacks ) {
152- callback ( data ) ;
153- }
154- for ( const callback of this . finallyCallbacks ) {
155- callback ( ) ;
156- }
153+ // Tail call return is important!
154+ return this . invokeCallbacks ( this . fulfilledCallbacks , value ) ;
157155 }
158156 }
159157
@@ -163,55 +161,85 @@ export class __TS__Promise<T> implements Promise<T> {
163161 this . state = PromiseState . Rejected ;
164162 this . rejectionReason = reason ;
165163
166- for ( const callback of this . rejectedCallbacks ) {
167- callback ( reason ) ;
164+ // Tail call return is important!
165+ return this . invokeCallbacks ( this . rejectedCallbacks , reason ) ;
166+ }
167+ }
168+
169+ private invokeCallbacks < T > ( callbacks : ReadonlyArray < ( value : T ) => void > , value : T ) : void {
170+ const callbacksLength = callbacks . length ;
171+ const finallyCallbacks = this . finallyCallbacks ;
172+ const finallyCallbacksLength = finallyCallbacks . length ;
173+
174+ if ( callbacksLength !== 0 ) {
175+ for ( const i of $range ( 1 , callbacksLength - 1 ) ) {
176+ callbacks [ i - 1 ] ( value ) ;
168177 }
169- for ( const callback of this . finallyCallbacks ) {
170- callback ( ) ;
178+ // Tail call optimization for a common case.
179+ if ( finallyCallbacksLength === 0 ) {
180+ return callbacks [ callbacksLength - 1 ] ( value ) ;
171181 }
182+ callbacks [ callbacksLength - 1 ] ( value ) ;
183+ }
184+
185+ if ( finallyCallbacksLength !== 0 ) {
186+ for ( const i of $range ( 1 , finallyCallbacksLength - 1 ) ) {
187+ finallyCallbacks [ i - 1 ] ( ) ;
188+ }
189+ return finallyCallbacks [ finallyCallbacksLength - 1 ] ( ) ;
172190 }
173191 }
174192
175193 private createPromiseResolvingCallback < TResult1 , TResult2 > (
176- f : FulfillCallback < T , TResult1 > | RejectCallback < TResult2 > ,
177- resolve : FulfillCallback < TResult1 | TResult2 , unknown > ,
178- reject : RejectCallback < unknown >
194+ f : PromiseResolveCallback < T , TResult1 > | PromiseRejectCallback < TResult2 > ,
195+ resolve : ( data : TResult1 | TResult2 ) => void ,
196+ reject : ( reason : any ) => void
179197 ) {
180- return ( value : T ) => {
181- try {
182- this . handleCallbackData ( f ( value ) , resolve , reject ) ;
183- } catch ( e ) {
184- // If a handler function throws an error, the promise returned by then gets rejected with the thrown error as its value
185- reject ( e ) ;
198+ return ( value : T ) : void => {
199+ const [ success , resultOrError ] = pcall <
200+ undefined ,
201+ [ T ] ,
202+ TResult1 | PromiseLike < TResult1 > | TResult2 | PromiseLike < TResult2 >
203+ > ( f , undefined , value ) ;
204+ if ( ! success ) {
205+ // Tail call return is important!
206+ return reject ( resultOrError ) ;
186207 }
208+ // Tail call return is important!
209+ return this . handleCallbackValue ( resultOrError , resolve , reject ) ;
187210 } ;
188211 }
189212
190- private handleCallbackData < TResult1 , TResult2 , TResult extends TResult1 | TResult2 > (
191- data : TResult | PromiseLike < TResult > ,
192- resolve : FulfillCallback < TResult1 | TResult2 , unknown > ,
193- reject : RejectCallback < unknown >
194- ) {
195- if ( isPromiseLike < TResult > ( data ) ) {
196- const nextpromise = data as __TS__Promise < TResult > ;
213+ private handleCallbackValue < TResult1 , TResult2 , TResult extends TResult1 | TResult2 > (
214+ value : TResult | PromiseLike < TResult > ,
215+ resolve : ( data : TResult1 | TResult2 ) => void ,
216+ reject : ( reason : any ) => void
217+ ) : void {
218+ if ( isPromiseLike < TResult > ( value ) ) {
219+ const nextpromise = value as __TS__Promise < TResult > ;
197220 if ( nextpromise . state === PromiseState . Fulfilled ) {
198221 // If a handler function returns an already fulfilled promise,
199- // the promise returned by then gets fulfilled with that promise's value
200- resolve ( nextpromise . value ! ) ;
222+ // the promise returned by then gets fulfilled with that promise's value.
223+ // Tail call return is important!
224+ return resolve ( nextpromise . value ! ) ;
201225 } else if ( nextpromise . state === PromiseState . Rejected ) {
202226 // If a handler function returns an already rejected promise,
203- // the promise returned by then gets fulfilled with that promise's value
204- reject ( nextpromise . rejectionReason ) ;
227+ // the promise returned by then gets fulfilled with that promise's value.
228+ // Tail call return is important!
229+ return reject ( nextpromise . rejectionReason ) ;
205230 } else {
206231 // If a handler function returns another pending promise object, the resolution/rejection
207232 // of the promise returned by then will be subsequent to the resolution/rejection of
208233 // the promise returned by the handler.
209- data . then ( resolve , reject ) ;
234+ // We cannot use `then` because we need to do tail call, and `then` returns a Promise.
235+ // `resolve` and `reject` should never throw.
236+ return nextpromise . addCallbacks ( resolve , reject ) ;
210237 }
211238 } else {
212239 // If a handler returns a value, the promise returned by then gets resolved with the returned value as its value
213240 // If a handler doesn't return anything, the promise returned by then gets resolved with undefined
214- resolve ( data ) ;
241+ // Tail call return is important!
242+ return resolve ( value ) ;
215243 }
216244 }
217245}
0 commit comments