Source: timers/AsyncTimer.js

import { SerialTimer }  from 'virtjs/devices/timers/SerialTimer';
import { makeFastTick } from 'virtjs/devices/timers/utils';

export class AsyncTimer {

    /**
     * An AsyncTimer is an asynchronous timer device. You can use it to run your emulator without blocking your main thread. However, unless you really want to implement a new asynchronous device on top of a new API, you're probably looking for {@link AnimationFrameTimer} for browser environments, or {@link ImmediateTimer} for Node.js environments.
     *
     * @constructor
     * @implements {Timer}
     *
     * @param {object} [options] - The timer options.
     * @param {function} [options.prepare] - The callback that will schedule the next cycle
     * @param {function} [options.cancel] - The callback that will abort the next cycle
     *
     * @see {@link AnimationFrameTimer}
     * @see {@link ImmediateTimer}
     */

    constructor({ prepare, cancel } = { }) {

        if (prepare)
            this.prepare = prepare;

        if (cancel)
            this.cancel = cancel;

        this.running = false;
        this.nested = false;

        this.loopHandler = null;
        this.fastLoop = null;

        this.timer = new SerialTimer();

    }

    nextTick(callback) {

        return this.timer.nextTick(callback);

    }

    cancelTick(handler) {

        return this.timer.cancelTick(handler);

    }

    start(beginning, ending) {

        if (this.running)
            throw new Error(`You can't start a timer that is already running`);

        if (this.nested)
            throw new Error(`You can't start a timer from its callbacks - use resume instead`);

        this.running = true;

        let resolve;
        let reject;

        let promise = new Promise((resolveFn, rejectFn) => {

            resolve = resolveFn;
            reject = rejectFn;

        });

        let fastTick = makeFastTick(beginning, ending, () => {

            this.timer.one();

        });

        let mainLoop = () => {

            if (!this.running) {

                resolve();

            } else try {

                this.prepare(mainLoop);

                this.nested = true;
                fastTick();
                this.nested = false;

            } catch (e) {

                this.running = false;
                this.nested = false;

                reject(e);

            }

        };

        this.prepare(mainLoop);

        return promise;

    }

    resume() {

        if (!this.nested)
            throw new Error(`You can't resume a timer from anywhere else than its callbacks - use start instead`);

        if (this.running)
            return;

        this.running = true;

    }

    stop() {

        if (!this.running)
            return;

        this.running = false;

    }

    /**
     * This method should be specialized, either via subclassing, or by passing the proper parameter when instanciating the timer.
     *
     * @protected
     *
     * @type {prepareCallback}
     */

    prepare() {

        throw new Error(`Unimplemented`);

    }

    /**
     * This method should be specialized, either via subclassing, or by passing the proper parameter when instanciating the timer.
     *
     * @protected
     *
     * @type {cancelCallback}
     */

    cancel() {

        throw new Error(`Unimplemented`);

    }

}