/**
 * Call queue for executing callbacks asynchronously.
 *
 * Prevents releasing Zalgo.
 *
 * Copyright (C) 2015 Martin Poelstra
 * License: MIT
 */ import { assert } from "./util" ; class CallQueue { [ index ] : any ; public length : number = 0 ; private _max : number = 1000 ; private _first : number = 0 ; return public push ( callback , arg ) { this [ this . length ++ ] = callback ; this [ this . length ++ ] = arg ; return this . length < this . _max ; } /**
     * Flush all callbacks in this queue.
     * Note that it is 'ok' for callbacks to throw an error;
     * the next call to flush() will flush the remainder of the queue.
     * When this function returns, the queue will be 'reset' to its beginning.
     */ public flush ( ) { while ( this . _first < this . length ) { const callback = this [ this . _first ] ; const arg = this [ this . _first + 1 ] ; this [ this . _first ] = this [ this . _first + 1 ] = undefined ; this . _first += 2 ; callback ( arg ) ; } this . length = 0 ; this . _first = 0 ; } public empty ( ) { return this . _first === this . length ; } } class Ring { /**
     * Reference to shared pool of reusable queues, owned by our owner (`Async`).
     */ private _pool : CallQueue [ ] ; /**
     * Ring of queues.
     * Guaranteed to always contain at least one queue.
     */ private _ring : CallQueue [ ] = [ new CallQueue ( ) ] ; /**
     * Queue to put new callbacks in, i.e. the last queue in the ring.
     * If `undefined`, a new queue will be obtained and added to ring on next enqueue.
     */ private _current : CallQueue | undefined = this . _ring [ 0 ] ; constructor ( pool ) { this . _pool = pool ; } /**
     * Add callback (and optional argument) to last queue in ring.
     * Automatically obtains new queue if current queue is full.
     */ public enqueue ( callback ) ; public enqueue < T > ( callback , arg ) ; public enqueue ( callback , arg ? ) { if ( ! this . _current ) { this . _current = this . _pool . pop ( ) ; if ( ! this . _current ) { this . _current = new CallQueue ( ) ; } this . _ring . push ( this . _current ) ; } if ( ! this . _current . push ( callback , arg ) ) { this . _current = undefined ; } } /**
     * Call all callbacks in all queues in this ring, until it is empty.
     * Note: it is 'OK' for a callback to throw an error; ring/queue state
     * will remain valid and remaining items will be flushed on next call
     * to `flush()`.
     */ public flush ( ) { while ( true ) { this . _ring [ 0 ] . flush ( ) ; if ( this . _ring . length === 1 ) { break ; } this . _pool . push ( this . _ring . shift ( ) ! ) ; } this . _current = this . _ring [ 0 ] ; } /**
     * Return true if no callbacks are enqueued in this ring.
     */ public empty ( ) { return this . _ring . length === 1 && this . _ring [ 0 ] . empty ( ) ; } } function defaultScheduler ( callback ) { if ( typeof setImmediate === "function" ) { setImmediate ( callback ) ; } else { setTimeout ( callback , 0 ) ; } } export class Async { private _pool : CallQueue [ ] = [ ] ; private _mainRing : Ring = new Ring ( this . _pool ) ; private _idleRing : Ring = new Ring ( this . _pool ) ; private _flushing : boolean = false ; private _scheduled : boolean = false ; private _scheduler ? : ( callback : ( ) => void ) => void = undefined ; /**
     * Configure alternative scheduler to use.
     * The scheduler function will be called with a flusher, which needs to be
     * executed to flush the queue. Note: the flusher may throw an
     * exception, if any of the callbacks on the queue throws one.
     * This will result in another flush to be scheduled before returning.
     *
     * Call with `undefined` to reset the scheduler to the default (setImmediate).
     *
     * Example usage (this is basically the default):
     *   setScheduler((flusher) => setImmediate(flusher));
     * Note: this is slightly different from just setScheduler(setImmediate), in that
     * the former allows overriding setImmediate in e.g. unit tests.
     */ public setScheduler ( scheduler ) { assert ( scheduler === undefined || scheduler === null || typeof scheduler === "function" ) ; this . _scheduler = scheduler ; } param callback param arg public enqueue ( callback ) ; public enqueue < T > ( callback , arg ) ; public enqueue ( callback , arg ? ) { if ( ! this . _flushing && ! this . _scheduled ) { this . _schedule ( ) ; } this . _mainRing . enqueue ( callback , arg ) ; } param callback param arg public enqueueIdle ( callback ) ; public enqueueIdle < T > ( callback , arg ) ; public enqueueIdle ( callback , arg ? ) { if ( ! this . _flushing && ! this . _scheduled ) { this . _schedule ( ) ; } this . _idleRing . enqueue ( callback , arg ) ; } /**
     * Flush callback queues.
     * First, the 'normal' callback queues are flushed until they are empty (i.e.
     * new callbacks that are added while executing will also be processed).
     * Then, the 'idle' queues are flushed (also until they are empty).
     * Flushing repeats until no more items are enqueued in normal or idle queues.
     * It is an error to call flush from within an enqueued callback.
     */ public flush ( ) { assert ( ! this . _flushing , "cannot recursively flush" ) ; this . _flushing = true ; try { while ( true ) { this . _mainRing . flush ( ) ; if ( this . _idleRing . empty ( ) ) { break ; } const emptyRing = this . _mainRing ; this . _mainRing = this . _idleRing ; this . _idleRing = emptyRing ; } } finally { this . _flushing = false ; if ( ( ! this . _mainRing . empty ( ) || ! this . _idleRing . empty ( ) ) && ! this . _scheduled ) { this . _schedule ( ) ; } } } private _flusher : ( ) => void = ( ) => this . _scheduledFlush ( ) ; private _schedule ( ) { assert ( ! this . _scheduled ) ; const scheduler = this . _scheduler || defaultScheduler ; scheduler ( this . _flusher ) ; this . _scheduled = true ; } private _scheduledFlush ( ) { this . _scheduled = false ; this . flush ( ) ; } } export let async = new Async ( ) ; export default async ;