66 ThrowingEventTarget ,
77 TypedEventListener ,
88 ValueOf ,
9+ prefixError ,
910} from "@miniflare/shared" ;
1011import { Response as BaseResponse , fetch as baseFetch } from "undici" ;
1112import { DOMException } from "./domexception" ;
@@ -178,18 +179,33 @@ export type WorkerGlobalScopeEventMap = {
178179
179180export class WorkerGlobalScope extends ThrowingEventTarget < WorkerGlobalScopeEventMap > { }
180181
182+ // true will be added to this set if #logUnhandledRejections is true so we
183+ // don't remove the listener on removeEventListener, and know to dispose it.
184+ type PromiseListenerSetMember =
185+ | TypedEventListener < PromiseRejectionEvent >
186+ | boolean ;
187+
188+ type PromiseListener =
189+ | {
190+ name : "unhandledRejection" ;
191+ set : Set < PromiseListenerSetMember > ;
192+ listener : ( reason : any , promise : Promise < any > ) => void ;
193+ }
194+ | {
195+ name : "rejectionHandled" ;
196+ set : Set < PromiseListenerSetMember > ;
197+ listener : ( promise : Promise < any > ) => void ;
198+ } ;
199+
181200export class ServiceWorkerGlobalScope extends WorkerGlobalScope {
182201 readonly #log: Log ;
183202 readonly #bindings: Context ;
184203 readonly #modules?: boolean ;
204+ readonly #logUnhandledRejections?: boolean ;
185205 #calledAddFetchEventListener = false ;
186206
187- readonly #rejectionHandledListeners = new Set <
188- TypedEventListener < PromiseRejectionEvent >
189- > ( ) ;
190- readonly #unhandledRejectionListeners = new Set <
191- TypedEventListener < PromiseRejectionEvent >
192- > ( ) ;
207+ readonly #unhandledRejection: PromiseListener ;
208+ readonly #rejectionHandled: PromiseListener ;
193209
194210 // Global self-references
195211 readonly global = this ;
@@ -199,12 +215,29 @@ export class ServiceWorkerGlobalScope extends WorkerGlobalScope {
199215 log : Log ,
200216 globals : Context ,
201217 bindings : Context ,
202- modules ?: boolean
218+ modules ?: boolean ,
219+ logUnhandledRejections ?: boolean
203220 ) {
204221 super ( ) ;
205222 this . #log = log ;
206223 this . #bindings = bindings ;
207224 this . #modules = modules ;
225+ this . #logUnhandledRejections = logUnhandledRejections ;
226+
227+ this . #unhandledRejection = {
228+ name : "unhandledRejection" ,
229+ set : new Set ( ) ,
230+ listener : this . #unhandledRejectionListener,
231+ } ;
232+ this . #rejectionHandled = {
233+ name : "rejectionHandled" ,
234+ set : new Set ( ) ,
235+ listener : this . #rejectionHandledListener,
236+ } ;
237+ // If we're logging unhandled rejections, register the process-wide listener
238+ if ( this . #logUnhandledRejections) {
239+ this . #maybeAddPromiseListener( this . #unhandledRejection, true ) ;
240+ }
208241
209242 // Only including bindings in global scope if not using modules
210243 Object . assign ( this , globals ) ;
@@ -248,24 +281,10 @@ export class ServiceWorkerGlobalScope extends WorkerGlobalScope {
248281 // Register process wide unhandledRejection/rejectionHandled listeners if
249282 // not already done so
250283 if ( type === "unhandledrejection" && listener ) {
251- if ( this . #unhandledRejectionListeners. size === 0 ) {
252- this . #log. verbose ( "Adding process unhandledRejection listener..." ) ;
253- process . prependListener (
254- "unhandledRejection" ,
255- this . #unhandledRejectionListener
256- ) ;
257- }
258- this . #unhandledRejectionListeners. add ( listener as any ) ;
284+ this . #maybeAddPromiseListener( this . #unhandledRejection, listener ) ;
259285 }
260286 if ( type === "rejectionhandled" && listener ) {
261- if ( this . #rejectionHandledListeners. size === 0 ) {
262- this . #log. verbose ( "Adding process rejectionHandled listener..." ) ;
263- process . prependListener (
264- "rejectionHandled" ,
265- this . #rejectionHandledListener
266- ) ;
267- }
268- this . #rejectionHandledListeners. add ( listener as any ) ;
287+ this . #maybeAddPromiseListener( this . #rejectionHandled, listener ) ;
269288 }
270289
271290 super . addEventListener ( type , listener , options ) ;
@@ -286,26 +305,10 @@ export class ServiceWorkerGlobalScope extends WorkerGlobalScope {
286305 // Unregister process wide rejectionHandled/unhandledRejection listeners if
287306 // no longer needed and not already done so
288307 if ( type === "unhandledrejection" && listener ) {
289- const registered = this . #unhandledRejectionListeners. size > 0 ;
290- this . #unhandledRejectionListeners. delete ( listener as any ) ;
291- if ( registered && this . #unhandledRejectionListeners. size === 0 ) {
292- this . #log. verbose ( "Removing process unhandledRejection listener..." ) ;
293- process . removeListener (
294- "unhandledRejection" ,
295- this . #unhandledRejectionListener
296- ) ;
297- }
308+ this . #maybeRemovePromiseListener( this . #unhandledRejection, listener ) ;
298309 }
299310 if ( type === "rejectionhandled" && listener ) {
300- const registered = this . #rejectionHandledListeners. size > 0 ;
301- this . #rejectionHandledListeners. delete ( listener as any ) ;
302- if ( registered && this . #rejectionHandledListeners. size === 0 ) {
303- this . #log. verbose ( "Removing process rejectionHandled listener..." ) ;
304- process . removeListener (
305- "rejectionHandled" ,
306- this . #rejectionHandledListener
307- ) ;
308- }
311+ this . #maybeRemovePromiseListener( this . #rejectionHandled, listener ) ;
309312 }
310313
311314 super . removeEventListener ( type , listener , options ) ;
@@ -423,22 +426,50 @@ export class ServiceWorkerGlobalScope extends WorkerGlobalScope {
423426 return ( await Promise . all ( event [ kWaitUntil ] ) ) as WaitUntil ;
424427 }
425428
429+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
430+ #maybeAddPromiseListener( listener : PromiseListener , member : any ) : void {
431+ if ( listener . set . size === 0 ) {
432+ this . #log. verbose ( `Adding process ${ listener . name } listener...` ) ;
433+ process . prependListener ( listener . name as any , listener . listener as any ) ;
434+ }
435+ listener . set . add ( member ) ;
436+ }
437+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
438+ #maybeRemovePromiseListener( listener : PromiseListener , member : any ) : void {
439+ const registered = listener . set . size > 0 ;
440+ listener . set . delete ( member ) ;
441+ if ( registered && listener . set . size === 0 ) {
442+ this . #log. verbose ( `Removing process ${ listener . name } listener...` ) ;
443+ process . removeListener ( listener . name , listener . listener ) ;
444+ }
445+ }
446+ #resetPromiseListener( listener : PromiseListener ) : void {
447+ if ( listener . set . size > 0 ) {
448+ this . #log. verbose ( `Removing process ${ listener . name } listener...` ) ;
449+ process . removeListener ( listener . name , listener . listener ) ;
450+ }
451+ listener . set . clear ( ) ;
452+ }
453+
426454 // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
427455 #unhandledRejectionListener = ( reason : any , promise : Promise < any > ) : void => {
428456 const event = new PromiseRejectionEvent ( "unhandledrejection" , {
429457 reason,
430458 promise,
431459 } ) ;
432460 const notCancelled = super . dispatchEvent ( event ) ;
433- // If the event wasn't preventDefault()ed, remove the listener and cause
434- // an unhandled promise rejection again. This should terminate the program.
461+ // If the event wasn't preventDefault()ed,
435462 if ( notCancelled ) {
436- process . removeListener (
437- "unhandledRejection" ,
438- this . #unhandledRejectionListener
439- ) ;
440- // noinspection JSIgnoredPromiseFromCall
441- Promise . reject ( reason ) ;
463+ if ( this . #logUnhandledRejections) {
464+ // log if we're logging unhandled rejections
465+ this . #log. error ( prefixError ( "Unhandled Promise Rejection" , reason ) ) ;
466+ } else {
467+ // ...otherwise, remove the listener and cause an unhandled promise
468+ // rejection again. This should terminate the program.
469+ this . #resetPromiseListener( this . #unhandledRejection) ;
470+ // noinspection JSIgnoredPromiseFromCall
471+ Promise . reject ( reason ) ;
472+ }
442473 }
443474 } ;
444475
@@ -449,22 +480,7 @@ export class ServiceWorkerGlobalScope extends WorkerGlobalScope {
449480 } ;
450481
451482 [ kDispose ] ( ) : void {
452- if ( this . #unhandledRejectionListeners. size > 0 ) {
453- this . #log. verbose ( "Removing process unhandledRejection listener..." ) ;
454- process . removeListener (
455- "unhandledRejection" ,
456- this . #unhandledRejectionListener
457- ) ;
458- }
459- this . #unhandledRejectionListeners. clear ( ) ;
460-
461- if ( this . #rejectionHandledListeners. size > 0 ) {
462- this . #log. verbose ( "Removing process rejectionHandled listener..." ) ;
463- process . removeListener (
464- "rejectionHandled" ,
465- this . #rejectionHandledListener
466- ) ;
467- }
468- this . #rejectionHandledListeners. clear ( ) ;
483+ this . #resetPromiseListener( this . #unhandledRejection) ;
484+ this . #resetPromiseListener( this . #rejectionHandled) ;
469485 }
470486}
0 commit comments