import { ScopeFinishingError } from "./Errors" ; import { CancelFunction , Coroutine , CoroutineFactory , Result , ResultCallback , Resume , Suspender , } from "./Types" ; /**
 * Scope is used to start groups of coroutines that are canceled together. If any coroutine in the
 * scope throws an error, it will cancel all the remaining active coroutines within the scope.
 * Scopes canceled with an error will call errorCallback and bubble up errrors to their parent
 * scope.
 */ export class Scope { static nonCanceling = new Scope ( { isCancelable : false , errorCallback : ( error ) => { console . error ( error instanceof Error ? error . stack : error ) ; } } ) ; _cancelCallbacks = new Map < Coroutine < unknown > , CancelFunction > ( ) ; private _finishedCallbacks = new Set < ResultCallback < void > > ( ) ; private _subscopes = new Set < Scope > ( ) ; private _isFinishing = false ; private _isFinished = false ; private _isCanceled = false ; private readonly _isCancelable : boolean ; private _parent ? : Scope ; private _errorCallback ? : ( error : unknown , scope : Scope ) => void ; constructor ( options ? ) { this . _parent = options ?. parent ; this ?. _parent ?. _subscopes ?. add ( this ) ; this . _errorCallback = options ?. errorCallback ; this . _isCancelable = options ?. isCancelable ?? true ; } /**
     * Returns true if scope is not canceled.
     */ isActive ( ) { return ! this . _isCanceled ; } param { CoroutineFactory < T > } factory launch < T > ( factory ) { this . _checkIfFinishing ( ) ; const coroutine = factory . call ( this ) ; this . _resume ( coroutine , { value : undefined } ) ; return ( ) => { this . _cancelCallbacks . get ( coroutine ) ?. call ( undefined ) ; } ; } /**
     * Cancels all coroutines in this scope. All pending suspenders are canceled and all active
     * coroutines finally block is called. If their finally block suspends, they are migrated to a
     * non-canceling scope.
     */ cancel ( ) { if ( ! this . _isCancelable || this . _isCanceled ) { return ; } this . _isCanceled = true ; this . _parent ?. _subscopes ?. delete ( this ) ; for ( const scope of this . _subscopes ) { scope . cancel ( ) ; } for ( const cancelCallback of this . _cancelCallbacks . values ( ) ) { cancelCallback ( ) ; } } param { Suspender < T > } suspender return { Coroutine < T > } * suspend < T > ( suspender ) { return ( yield suspender ) as T ; } param { Suspender < A > } susA param { Suspender < B > } susB return { [ A , B ] } * suspend2 < A , B > ( susA , susB ) { return [ yield * this . suspend ( susA ) , yield * this . suspend ( susB ) ] ; } param { Suspender < A > } susA param { Suspender < B > } susB param { Suspender < C > } susC return { [ A , B , C ] } * suspend3 < A , B , C > ( susA , susB , susC ) { return [ yield * this . suspend ( susA ) , yield * this . suspend ( susB ) , yield * this . suspend ( susC ) ] ; } param { Suspender < A > } susA param { Suspender < B > } susB param { Suspender < C > } susC param { Suspender < D > } susD return { [ A , B , C , D ] } * suspend4 < A , B , C , D > ( susA , susB , susC , susD ) { return [ yield * this . suspend ( susA ) , yield * this . suspend ( susB ) , yield * this . suspend ( susC ) , yield * this . suspend ( susD ) , ] ; } param { CoroutineFactory < T > } factory * call < T > ( factory ) { if ( this . _isCanceled ) { return yield * Scope . nonCanceling . call < T > ( factory ) ; } else { const subscope = new Scope ( { parent : this } ) ; const suspender = subscope . callAsync < T > ( factory ) ; yield subscope . _finish ( ) ; return ( yield suspender ) as T ; } } param { CoroutineFactory < T > } factory return { Suspender < T > } callAsync < T > ( factory ) { this . _checkIfFinishing ( ) ; let result ; let resultCallback ; const coroutine = factory . call ( this ) ; this . _resume ( coroutine , { value : undefined } , ( res ) => { if ( resultCallback !== undefined ) { resultCallback ( res ) ; } else { result = res ; } } ) ; return ( resCallback ) => { if ( result !== undefined ) { resCallback ( result ) ; } else { resultCallback = resCallback ; } return ( ) => { this . _cancelCallbacks . get ( coroutine ) ?. call ( undefined ) ; } ; } ; } param { Coroutine < T > } coroutine param { Resume < unknown > } resume param { ResultCallback < T > } resultCallback _resume < T > ( coroutine , resume , resultCallback ? ) { try { const iteratorResult = resume . tag === `error` ? coroutine . throw ( resume . error ) : resume . tag === `finish` ? coroutine . return ( undefined as any ) : coroutine . next ( resume . value ) ; if ( iteratorResult . done ) { if ( resume . tag !== `finish` ) { resultCallback ?. call ( undefined , { value : iteratorResult . value } ) ; } if ( this . _isFinishing && this . _cancelCallbacks . size === 0 ) { this . _isFinished = true ; for ( const finishedCallback of this . _finishedCallbacks ) { finishedCallback ( { value : undefined } ) ; } this . _parent ?. _subscopes ?. delete ( this ) ; } } else { let wasCallbackCalled = false ; const cancelCallback = iteratorResult . value ( ( value ) => { if ( ! wasCallbackCalled ) { wasCallbackCalled = true ; this . _cancelCallbacks . delete ( coroutine ) ; this . _resume ( coroutine , value , resultCallback ) ; } } ) ; if ( ! wasCallbackCalled ) { this . _cancelCallbacks . set ( coroutine , ( ) => { if ( ! wasCallbackCalled ) { wasCallbackCalled = true ; this . _cancelCallbacks . delete ( coroutine ) ; if ( cancelCallback !== undefined ) { cancelCallback ( ) ; } Scope . nonCanceling . _resume ( coroutine , { tag : `finish` } , resultCallback ) ; if ( this . _isFinishing && this . _cancelCallbacks . size === 0 ) { this . _isFinished = true ; for ( const finishedCallback of this . _finishedCallbacks ) { finishedCallback ( { value : undefined } ) ; } this . _parent ?. _subscopes ?. delete ( this ) ; } } } ) ; } } } catch ( error ) { if ( error instanceof ScopeFinishingError ) { console . error ( error . stack ) ; } else { this . _cancelWithError ( error ) ; } } } return { Suspender < T > } private _finish ( ) { return ( resultCallback ) => { if ( this . _isFinished ) { resultCallback ( { value : undefined } ) ; return ; } else { this . _isFinishing = true ; if ( this . _cancelCallbacks . size === 0 ) { this . _isFinished = true ; resultCallback ( { value : undefined } ) ; return ; } else { const finishCallback = ( ) => { resultCallback ( { value : undefined } ) ; } ; this . _finishedCallbacks . add ( finishCallback ) ; return ( ) => { this . _finishedCallbacks . delete ( finishCallback ) ; } ; } } } ; } param error _cancelWithError ( error ) { if ( ! this . _isCancelable || this . _isCanceled ) { return ; } this . _isCanceled = true ; for ( const scope of this . _subscopes ) { scope . cancel ( ) ; } for ( const cancelCallback of this . _cancelCallbacks . values ( ) ) { cancelCallback ( ) ; } try { this . _errorCallback ?. call ( undefined , error , this ) ; } catch ( anotherError ) { console . error ( `error in error callback ${ anotherError . stack }` ) ; } this . _parent ?. _subscopes . delete ( this ) ; this . _parent ?. _cancelWithError ( error ) ; } /**
     * Checks if new coroutines can be created.
     */ private _checkIfFinishing ( ) { if ( this . _isFinishing ) { throw new ScopeFinishingError ( ) ; } } }