UNPKG

asyncawait

Version:
141 lines (124 loc) 5.83 kB
import Promise = require('bluebird'); import _ = require('lodash'); import types = require('../types'); import FiberMgr = require('./fiberManager'); import RunContext = require('./runContext'); import Semaphore = require('./semaphore'); import Config = require('./config'); import defer = require('./defer'); import await = require('../await/index'); export = AsyncIterator; /** * Asynchronous analogue to an ES6 Iterator. Rather than return each value/done * result synchronously, the next() function notifies a promise and/or callback * when the next result is ready. */ class AsyncIterator { /** Construct a new AsyncIterator instance. This will create a fiber. */ constructor(runContext: RunContext, semaphore: Semaphore, returnValue: string, acceptsCallback: boolean) { this._runContext = runContext; this._semaphore = semaphore; this._fiber = FiberMgr.create(); this._returnValue = returnValue; this._acceptsCallback = acceptsCallback; } /** Fetch the next result from the iterator. */ next(callback?: (err, result) => void) { // Configure the run context. if (this._acceptsCallback) { this._runContext.callback = callback; // May be null, in which case it won't be used. } if (this._returnValue !== Config.NONE) { var resolver = defer(); this._runContext.resolver = resolver; } // Remove concurrency restrictions for nested calls, to avoid race conditions. if (FiberMgr.isExecutingInFiber()) this._semaphore = Semaphore.unlimited; // Run the fiber until it either yields a value or completes. For thunks, this is a lazy operation. if (this._returnValue === Config.THUNK) { var thunk: types.Thunk<any> = (done?) => { if (done) resolver.promise.then(val => done(null, val), err => done(err)); this._semaphore.enter(() => this._fiber.run(this._runContext)); this._runContext.done = () => this._semaphore.leave(); }; } else { this._semaphore.enter(() => this._fiber.run(this._runContext)); this._runContext.done = () => this._semaphore.leave(); } // Return the appropriate value. switch (this._returnValue) { case Config.PROMISE: return resolver.promise; case Config.THUNK: return thunk; case Config.RESULT: return await (resolver.promise); case Config.NONE: return; } } /** Enumerate the entire iterator, calling callback with each result. */ forEach(callback: (value) => void, doneCallback?: (err?) => void): any { // Create a function that calls next() in an asynchronous loop until the iteration is complete. var run, runCtx = this._runContext; if (this._returnValue === Config.RESULT) run = () => stepAwaited(() => this.next()); else if (this._returnValue === Config.THUNK) run = () => this.next()(stepCallback); else if (this._acceptsCallback) run = () => this.next(stepCallback); else run = () => this.next().then(stepResolved, endOfIteration); // Configure the resolver and callback to be invoked at the end of the iteration. if (this._returnValue === Config.PROMISE || this._returnValue === Config.THUNK) { var doneResolver = defer(); } if (!this._acceptsCallback) doneCallback = null; // Execute the entire iteration. For thunks, this is a lazy operation. if (this._returnValue === Config.THUNK) { var thunk: types.Thunk<any> = (done?) => { if (done) doneResolver.promise.then(val => done(null, val), err => done(err)); run(); } } else { run(); } // Return the appropriate value. switch (this._returnValue) { case Config.PROMISE: return doneResolver.promise; case Config.THUNK: return thunk; case Config.RESULT: return undefined; case Config.NONE: return undefined; } // These functions handle stepping through and finalising the iteration. function stepAwaited(next) { try { while (true) { var item = next(); if (item.done) return endOfIteration(); callback(item.value); } } catch (err) { endOfIteration(err); throw err; } } function stepCallback(err, result) { if (err || result.done) return endOfIteration(err); callback(result.value); setImmediate(run); } function stepResolved(result) { if (result.done) return endOfIteration(); callback(result.value); setImmediate(run); } function endOfIteration(err?) { if (doneCallback) err ? doneCallback(err) : doneCallback(); if (doneResolver) { if (FiberMgr.isExecutingInFiber()) { runCtx.resolver = doneResolver; // FiberManager will handle it } else { err ? doneResolver.reject(err) : doneResolver.resolve(null); } } } } /** Release resources associated with this object (i.e., the fiber). */ destroy() { this._fiber = null; } private _runContext: RunContext; private _semaphore: Semaphore; private _fiber: Fiber; private _returnValue: string; private _acceptsCallback: boolean; }