'use strict' // like a promise // if a value is added, it is queued until the first then() // if then() is called before a value is available, // the returned promise is deferred // and queued up for later resolution eg when the next value is available or the // Result is completed. const CLOSED = Promise.resolve() module.exports = class Result { constructor () { this._isClosed = false this._pendingPromises = [] this._pendingVals = [] // TODO Nice to have ...array argument -> add() } add (val) { if (this.isClosed) { throw new Error('cannot add() to a closed Result') } if (this._pendingPromises.length > 0) { this._finishPromises(val) } else { this._pendingVals.push(val) } } get isClosed () { return this._isClosed } get hasValues () { return this._pendingVals.length > 0 } /** * Requests the next value, wrapped in a promise. * If no more values are available and the Result * is closed, a Promise resolved to undefined is returned. * Else, the returned promise will resolve or reject * when the next value is added. * * @param {Function} res * @param {Function} rej * @return {Promise} */ then (res, rej) { if (this.hasValues) { const val = this._pendingVals.shift() const method = val instanceof Error ? 'reject' : 'resolve' return Promise[method](val).then(res, rej) } if (this.isClosed) { // no more values will ever be available return CLOSED.then(res, rej) } const p = new Promise((resolve, reject) => { this._pendingPromises.push({ resolve, reject }) }) return p.then(res, rej) } close (val) { this.add(val) this._isClosed = true } _finishPromises (val) { const method = val instanceof Error ? 'reject' : 'resolve' let promise while ((promise = this._pendingPromises.shift())) { promise[method](val) } } }