ts-stream
Version:
Type-safe object streams with seamless support for backpressure, ending, and error handling
844 lines • 37.8 kB
JavaScript
"use strict";
/**
* Promise-based object stream with seamless support for back-pressure and error
* handling, written in Typescript.
*
* Copyright (C) 2015 Martin Poelstra
* License: MIT
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Stream = exports.AlreadyHaveReaderError = exports.WriteAfterEndError = void 0;
/* istanbul ignore next */ // ignores Typescript's __extend() function
var BaseError_1 = require("./BaseError");
var Transform_1 = require("./Transform");
var util_1 = require("./util");
/**
* Used when writing to an already-ended stream.
*/
var WriteAfterEndError = /** @class */ (function (_super) {
__extends(WriteAfterEndError, _super);
function WriteAfterEndError() {
return _super.call(this, "WriteAfterEndError", "stream already ended") || this;
}
return WriteAfterEndError;
}(BaseError_1.default));
exports.WriteAfterEndError = WriteAfterEndError;
/**
* Used when read callback(s) have already been attached.
*/
var AlreadyHaveReaderError = /** @class */ (function (_super) {
__extends(AlreadyHaveReaderError, _super);
function AlreadyHaveReaderError() {
return _super.call(this, "AlreadyHaveReaderError", "stream already has a reader") || this;
}
return AlreadyHaveReaderError;
}(BaseError_1.default));
exports.AlreadyHaveReaderError = AlreadyHaveReaderError;
/**
* Special internal 'error' value to indicate normal stream end.
*/
var EOF = new Error("eof");
/**
* Special end-of-stream value, optionally signalling an error.
*/
var Eof = /** @class */ (function () {
/**
* Create new end-of-stream value, optionally signalling an error.
* @param error Optional Error value
* @param result Optional final result value of `result()`
*/
function Eof(error, result) {
this.error = error;
this.result = result;
}
return Eof;
}());
/**
* Object stream with seamless support for backpressure, ending and error
* handling.
*/
var Stream = /** @class */ (function () {
function Stream() {
var _this = this;
/**
* Writers waiting for `_reader` to retrieve and process their value.
*/
this._writers = [];
/**
* Resolved to a rejection when `abort()` is called.
*/
this._abortDeferred = util_1.defer();
/**
* Resolved to the result of calling `_ender`, then the `result` property of
* the end-of-stream value.
*/
this._resultDeferred = util_1.defer();
/**
* Bound callback to be passed as handlers to Promise.then()
*/
this._pumper = function () { return _this._pump(); };
}
/**
* Write value (or promise for value) to stream.
*
* Writer is blocked until the value is read by the read handler passed to
* `forEach()`, and the value returned by that read handler is resolved.
*
* It is an error to write an `undefined` value (as this is a common
* programming error). Writing a promise for a void is currently allowed,
* but discouraged.
*
* The promise returned by `write()` will be rejected with the same reason if:
* - the written value is a PromiseLike that resolves to a rejection
* - the read handler throws an error or returns a rejected promise
* It is still possible to write another value after that, or e.g. `end()`
* the stream with or without an error.
*
* @param value Value to write, or promise for it
* @return Void-promise that resolves when value was processed by reader
*/
Stream.prototype.write = function (value) {
if (value === undefined) {
// Technically, we could allow this, but it's a common programming
// error to forget to return a value, and it's arguable whether it's
// useful to have a stream of void's, so let's prevent it for now.
// NOTE: This behaviour may change in the future
// NOTE: writeEach() currently DOES use `undefined` to signal EOF
// TODO: prevent writing a void PromiseLike too?
return Promise.reject(new TypeError("cannot write void value, use end() to end the stream"));
}
var writeDone = util_1.defer();
this._writers.push({
resolveWrite: writeDone.resolve,
value: util_1.track(Promise.resolve(value)),
});
this._pump();
return writeDone.promise;
};
/**
* End the stream, optionally passing an error.
*
* Already pending writes will be processed by the reader passed to
* `forEach()` before passing the end-of-stream to its end handler.
*
* The returned promise will resolve after the end handler has finished
* processing. It is rejected if the end handler throws an error or returns
* a rejected promise.
*
* All calls to `write()` or `end()` after the first `end()` will be
* rejected with a `WriteAfterEndError`.
*
* By default, this stream's `result()` will be resolved when `end()`
* resolves, or rejected with the error if `end()` is called with an error.
* It is possible to let this stream's `result()` 'wait' until any upstream
* streams have completed by e.g. passing that upstream's `result()` as the
* second argument to `end()`.
*
* Note: even if a stream is aborted, it is still necessary to call `end()`
* to allow any resources to correctly be cleaned up.
*
* @param error Optional Error to pass to `forEach()` end handler
* @param result Optional promise that determines final value of `result()`
* @return Void-promise that resolves when `ended`-handler has processed the
* end-of-stream
*/
Stream.prototype.end = function (error, endedResult) {
if (!(error === undefined || error === null || error instanceof Error)) {
// tslint:disable-line:no-null-keyword
return Promise.reject(new TypeError("invalid argument to end(): must be undefined, null or Error object"));
}
var eof = new Eof(error, endedResult);
if (!this._ending && !this._ended) {
this._ending = eof;
}
var writeDone = util_1.defer();
var item = {
resolveWrite: writeDone.resolve,
value: eof,
};
this._writers.push(item);
this._pump();
return writeDone.promise;
};
/**
* Read all values from stream, optionally waiting for explicit stream end.
*
* `reader` is called for every written value.
*
* `ender` is called once, when the writer `end()`s the stream, either with
* or without an error.
*
* `reader` and `ender` callbacks can return a promise to indicate when the
* value or end-of-stream condition has been completely processed. This
* ensures that a fast writer can never overload a slow reader, and is
* called 'backpressure'.
*
* The `reader` callback is never called while its previously returned
* promise is still pending, and the `ender` callback is likewise only
* called after all reads have completed.
*
* The corresponding `write()` or `end()` operation is blocked until the
* value returned from the reader or ender callback is resolved. If the
* callback throws an error or the returned promise resolves to a rejection,
* the `write()` or `end()` will be rejected with it.
*
* All callbacks are always called asynchronously (i.e. some time after
* `forEach()`, `write()`, `end()` or `abort()` returns), and their `this`
* argument will be undefined.
*
* `aborter` is called once if the stream is aborted and has not ended yet.
* (I.e. it will be called if e.g. `ender`'s returned promise is still
* pending, to allow early termination, but it will no longer be called
* if its promise has resolved).
*
* The `aborter` callback can be called while a reader callback's promise is
* still pending, and should try to let `reader` or `ender` finish as fast
* as possible.
*
* Note that even when a stream is aborted, it still needs to be `end()`'ed
* correctly.
*
* If no `ender` is given, a default end handler is installed that directly
* acknowledges the end-of-stream, also in case of an error. Note that that
* error will still be returned from `forEach()`.
*
* If no `aborter` is given, an abort is ignored (but will still cause
* further writes to fail, and it will be reflected in the returned promise).
*
* It is an error to call `forEach()` multiple times, and it is not possible
* to 'detach' already attached callbacks. Reason is that the exact behaviour
* of such actions (e.g. block or simply ignore further writes) is application
* dependent, and should be implemented as a transform instead.
*
* The return value of `forEach()` is `result()`, a promise that resolves
* when all parts in the stream(-chain) have completely finished.
*
* @param reader Callback called with every written value
* @param ender Optional callback called when stream is ended
* @param aborter Optional callback called when stream is aborted
* @return Promise for completely finished stream, i.e. same promise as `result()`
*/
Stream.prototype.forEach = function (reader, ender, aborter) {
if (this.hasReader()) {
return Promise.reject(new AlreadyHaveReaderError());
}
if (!ender) {
// Default ender swallows errors, because they
// will already be signalled in the stream's
// `.result()` (and thus the output of `.forEach()`).
// See #35.
ender = util_1.noop;
}
this._reader = reader;
this._ender = ender;
this._aborter = aborter;
this._pump();
return this.result();
};
/**
* Signal that the stream is aborted: it will no longer read incoming
* elements and will no longer write elements except in the course of
* processing any pending asynchronous reader callbacks (i.e. unresolved
* Promises returned by `forEach()` or other stream iterators). Does not
* end the stream.
*
* An upstream source can handle this `abort()` by catching the exception
* from its own `aborted()` method--for example, to cancel pending fetch
* operations, or close a continuous data stream.
*
* If the stream's `forEach()` function provided an `aborter` callback and
* the stream is not yet ended, `aborter` will be called with the abort reason.
* This can be used to cancel any remaining operations inside the asynchronous
* reader callback.
*
* Once the last pending callback is resolved, any pending and future `write()`s
* to this stream will be rejected with the error provided to `abort()`.
*
* It is still necessary to explicitly `end()` the stream, to ensure that any
* resources can be cleaned up correctly both on the reader and writer side.
* The stream's `ender` callback will be called with the abort error (i.e. any
* error passed to `end()` is ignored.)
*
* The abort is ignored if the stream is already aborted.
*
* It's possible to abort an ended stream. This can be used to 'bubble' an
* abort signal to other parts in a chain of streams which may not have ended
* yet. It will not change the end-state of this part of the stream though.
*
* @param reason Optional Error value to signal a reason for the abort
*/
Stream.prototype.abort = function (reason) {
if (this._abortPromise) {
return;
}
if (!reason) {
reason = new Error("aborted");
}
this._abortDeferred.reject(reason);
this._abortPromise = this._abortDeferred.promise;
this._abortReason = reason;
this._pump();
};
/**
* Obtain promise that resolves to a rejection when `abort()` is called.
*
* Useful to pass abort to up- and down-stream sources.
*
* Note: this promise either stays pending, or is rejected. It is never
* fulfilled.
*
* @return Promise that is rejected with abort error when stream is aborted
*/
Stream.prototype.aborted = function () {
return this._abortDeferred.promise;
};
/**
* Obtain a promise that resolves when the stream has completely ended:
* - `end()` has been called (possibly with an Error),
* - `ender` callback has run and its returned promise resolved,
* - `end()`'s result parameter (if any) has been resolved.
*
* @return Promise resolved when stream has completely ended
*/
Stream.prototype.result = function () {
return this._resultDeferred.promise;
};
/**
* Determine whether `end()` has been called on the stream, but the stream
* is still processing it.
*
* @return true when `end()` was called but not acknowledged yet, false
* otherwise
*/
Stream.prototype.isEnding = function () {
return !!this._ending;
};
/**
* Determine whether stream has completely ended (i.e. end handler has been
* called and its return PromiseLike, if any, is resolved).
* @return true when stream has ended, false otherwise
*/
Stream.prototype.isEnded = function () {
return !!this._ended;
};
/**
* Determine whether `end()` has been called on the stream.
*
* @return true when `end()` was called
*/
Stream.prototype.isEndingOrEnded = function () {
return this.isEnding() || this.isEnded();
};
/**
* Determine whether `forEach()` callback(s) are currently attached to the
* stream.
*
* @return true when `forEach()` has been called on this stream
*/
Stream.prototype.hasReader = function () {
return !!this._reader;
};
/**
* Run all input values through a mapping callback, which must produce a new
* value (or promise for a value), similar to e.g. `Array`'s `map()`.
*
* Stream end in the input stream (normal or with error) will be passed to
* the output stream, after awaiting the result of the optional ender.
*
* Any error (thrown or rejection) in mapper or ender is returned to the
* input stream.
*
* @param mapper Callback which receives each value from this stream, and
* must produce a new value (or promise for a value)
* @param ender Called when stream is ending, result is waited for before
* passing on `end()`
* @param aborter Called when stream is aborted
* @return New stream with mapped values
*/
Stream.prototype.map = function (mapper, ender, aborter) {
var output = new Stream();
Transform_1.map(this, output, mapper, ender, aborter);
return output;
};
/**
* Run all input values through a filtering callback. If the filter callback
* returns a truthy value (or a promise for a truthy value), the input value
* is written to the output stream, otherwise it is ignored.
* Similar to e.g. `Array`'s `filter()`.
*
* Stream end in the input stream (normal or with error) will be passed to
* the output stream, after awaiting the result of the optional ender.
*
* Any error (thrown or rejection) in mapper or ender is returned to the
* input stream.
*
* @param filterer Callback which receives each value from this stream,
* input value is written to output if callback returns a
* (promise for) a truthy value.
* @param ender Called when stream is ending, result is waited for before
* passing on `end()`
* @param aborter Called when stream is aborted
* @return New stream with filtered values.
*/
Stream.prototype.filter = function (filterer, ender, aborter) {
var output = new Stream();
Transform_1.filter(this, output, filterer, ender, aborter);
return output;
};
/**
* Reduce the stream into a single value by calling a reducer callback for
* each value in the stream. Similar to `Array#reduce()`.
*
* The output of the previous call to `reducer` (aka `accumulator`) is given
* as the first argument of the next call. For the first call, either the
* `initial` value to `reduce()` is passed, or the first value of the stream
* is used (and `current` will be the second value).
*
* The result of `reduce()` is a promise for the last value returned by
* `reducer` (or the initial value, if there were no calls to `reducer`).
* If no initial value could be determined, the result is rejected with a
* TypeError.
* If the stream is ended with an error, the result is rejected with that
* error.
*
* It is possible for `reducer` to return a promise for its result.
*
* If the `reducer` throws an error or returns a rejected promise, the
* originating `write()` will fail with that error.
*
* Examples:
* s.reduce((acc, val) => acc + val); // sums all values
* s.reduce((acc, val) => { acc.push(val); return acc; }, []); // toArray()
*
* @param reducer Callback called for each value in the stream, with
* accumulator, current value, index of current value, and
* this stream.
* @param initial Optional initial value for accumulator. If no initial
* value is given, first value of stream is used.
* @return Promise for final accumulator.
*/
Stream.prototype.reduce = function (
// tslint:disable-next-line:unified-signatures
reducer, initial) {
var _this = this;
var haveAccumulator = arguments.length === 2;
var accumulator = initial;
var index = 0;
return this.forEach(function (value) {
if (!haveAccumulator) {
accumulator = value;
haveAccumulator = true;
index++;
}
else {
return Promise.resolve(reducer(accumulator, value, index++, _this)).then(function (newAccumulator) { return (accumulator = newAccumulator); });
}
}).then(function () {
if (!haveAccumulator) {
return Promise.reject(new TypeError("cannot reduce() empty stream without initial value"));
}
return accumulator;
});
};
/**
* Read all stream values into an array.
*
* Returns a promise that resolves to that array if the stream ends
* normally, or to the error if the stream is ended with an error.
*
* @return Promise for an array of all stream values
*/
Stream.prototype.toArray = function () {
var result = [];
return this.forEach(function (value) {
result.push(value);
}).then(function () { return result; });
};
/**
* Read all values and end-of-stream from this stream, writing them to
* `writable`.
*
* @param writable Destination stream
* @return The stream passed in, for easy chaining
*/
Stream.prototype.pipe = function (writable) {
var _this = this;
writable.aborted().catch(function (err) { return _this.abort(err); });
this.aborted().catch(function (err) { return writable.abort(err); });
this.forEach(function (value) { return writable.write(value); }, function (error) { return writable.end(error, _this.result()); });
return writable;
};
/**
* Return a new stream with the results of running the given
* transform.
*
* @param transformer Function that receives this stream and result stream
* as inputs.
* @return Readable stream with the transformed results
*/
Stream.prototype.transform = function (transformer) {
var output = new Stream();
transformer(this, output);
return output;
};
/**
* Repeatedly call `writer` and write its returned value (or promise for it)
* to the stream.
* The stream is ended when `writer` returns `undefined` (or a promise that
* resolves to `undefined`).
*
* `writer` is only called again when its previously returned value has been
* processed by the stream.
*
* If writing of a value fails (either by the `writer` throwing an error,
* returning a rejection, or the write call failing), the stream is aborted
* and ended with that error.
*
* `ender` is always called once, just before the stream is ended. I.e.
* after `writer` returned (a promise for) `undefined` or the stream is
* aborted.
* It can be used to e.g. close a resource such as a database.
* Note: `ender` will only be called when `writer`'s promise (if any) has
* resolved.
*
* `aborter` is called once iff the stream is aborted. It can be called
* while e.g. a promise returned from the writer or ender is still pending,
* and can be used to make sure that that promise is resolved/rejected
* sooner.
* Note: the aborter should be considered a 'signal to abort', but cleanup
* of resources should be done in the `ender` (i.e. it cannot return a
* promise).
* It can be called (long) after `ender` has been called, because a stream
* can be aborted even after it is already ended, which is useful if this
* stream element is part of a larger chain of streams.
* An aborter must never throw an error.
*
* @param writer Called when the next value can be written to the stream,
* should return (a promise for) a value to be written,
* or `undefined` (or void promise) to end the stream.
* Will always be called asynchronously.
* @param ender Optional callback called once after `writer` indicated
* end-of-stream, or when the stream is aborted (and
* previously written value resolved/rejected). It's called
* without an argument if stream was not aborted (yet), and
* the abort reason if it was aborted (`aborter` will have
* been called, too). Will always be called asynchronously.
* @param aborter Optional callback called once when stream is aborted.
* Receives abort reason as its argument. Should be used
* to prematurely terminate any pending promises of
* `writer` or `ender`. Will always be called
* asynchronously. Can be called before and after
* `writer` or `ender` have been called, even when `ender`
* is completely finished (useful to e.g. abort other streams, which may
* not be aborted yet).
* Must not throw any errors, will lead to unhandled
* rejected promise if it does.
* @return Promise for completely finished stream, i.e. same promise as `result()`
*/
Stream.prototype.writeEach = function (writer, ender, aborter) {
var _this = this;
/**
* Call aborter (if any) and convert any thrown error into
* unhandled rejection. Unset aborter to prevent calling it
* again later.
*/
var callAborter = function (abortReason) {
if (!aborter) {
return;
}
try {
var callback = aborter;
aborter = undefined;
callback(abortReason);
}
catch (aborterError) {
// Convert into unhandled rejection. There's not really
// a sensible way to convert it into something else.
// One might think to pass it to the ender, which may
// be undefined, or this.end(), but that seams rather
// unexpected to occassionally have to handle an error
// from one specific aborter, whereas other aborters's
// errors will also lead to unhandled rejections.
// Note: the most sensible thing to do now, is to
// terminate the program.
Promise.reject(aborterError);
}
};
var worker = function () { return __awaiter(_this, void 0, void 0, function () {
var value, writeError_1, endError, error_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 7, 8, 14]);
_a.label = 1;
case 1:
if (!!this._abortPromise) return [3 /*break*/, 6];
return [4 /*yield*/, writer()];
case 2:
value = _a.sent();
if (!(value === undefined)) return [3 /*break*/, 3];
return [3 /*break*/, 6];
case 3: return [4 /*yield*/, this.write(value)];
case 4:
_a.sent();
_a.label = 5;
case 5: return [3 /*break*/, 1];
case 6: return [3 /*break*/, 14];
case 7:
writeError_1 = _a.sent();
this.abort(writeError_1);
return [3 /*break*/, 14];
case 8:
endError = this._abortReason;
if (this._abortReason) {
callAborter(this._abortReason);
}
if (!ender) return [3 /*break*/, 12];
_a.label = 9;
case 9:
_a.trys.push([9, 11, , 12]);
return [4 /*yield*/, (this._abortReason
? ender(this._abortReason)
: ender())];
case 10:
_a.sent();
return [3 /*break*/, 12];
case 11:
error_1 = _a.sent();
endError = error_1;
return [3 /*break*/, 12];
case 12: return [4 /*yield*/, this.end(endError)];
case 13:
_a.sent();
return [7 /*endfinally*/];
case 14: return [2 /*return*/];
}
});
}); };
// Asynchronously call worker, abort on error
Promise.resolve()
.then(worker)
.catch(function (error) { return _this.abort(error); });
// Ensure aborter is asynchronously called if necessary
this.aborted().catch(callAborter);
return this.result();
};
// TODO Experimental
// TODO Not sure whether a 'reverse' function confuses more than it helps
Stream.prototype.mappedBy = function (mapper) {
var input = new Stream();
Transform_1.map(input, this, mapper);
return input;
};
// TODO Experimental
// TODO Not sure whether a 'reverse' function confuses more than it helps
Stream.prototype.filterBy = function (filterer) {
var input = new Stream();
Transform_1.filter(input, this, filterer);
return input;
};
/**
* Return a Stream for all values in the input array.
*
* The stream is ended as soon as the first `undefined` value is
* encountered.
*
* The array itself, and/or the values in the array can also be promises.
*
* @see result() to wait for completion
* @see writeEach() for error handling behavior
*
* @param data (Promise for) input array of (promises for) values
* @return Stream of all values in the input array
*/
Stream.from = function (data) {
var stream = new Stream();
var i = 0;
if (Array.isArray(data)) {
stream.writeEach(function () { return data[i++]; });
}
else {
Promise.resolve(data).then(function (resolvedArray) {
stream.writeEach(function () { return resolvedArray[i++]; });
});
}
return stream;
};
/**
* Pump written values to `_reader` and `_ender`, resolve results of
* `write()` and `end()`.
*/
Stream.prototype._pump = function () {
var _this = this;
// Call abort handler, if necessary
if (this._abortPromise && this._aborter) {
// Make sure to call it asynchronously, and without a 'this'
// TODO: can any error thrown from the aborter be handled?
util_1.swallowErrors(this._abortPromise.catch(this._aborter));
this._aborter = undefined;
}
// If waiting for a reader/ender, wait some more or handle it
if (this._readBusy) {
if (this._readBusy.isPending) {
// Pump is already attached to _readBusy, so just wait for that
// to be resolved
return;
}
// Previous reader/ender has resolved, return its result to the
// corresponding write() or end() call
this._writers.shift().resolveWrite(this._readBusy.promise);
if (this._endPending) {
var result_1 = this._endPending.result;
this._ended = this._endPending.error || EOF;
this._ending = undefined;
this._endPending = undefined;
this._aborter = undefined; // no longer call aborter after end handler has finished
var p = void 0;
// Determine final result()
if (result_1) {
// Explicit result promise was passed to end().
// Make sure to wait until the ender is fully completed,
// which can be fulfilled or rejected. Ender's rejection is
// ignored here, because it is already passed upstream, and
// result then comes back downstream. This allows upstream to
// decide how to handle a downstream ender's failure.
p = this._readBusy.promise
.then(undefined, util_1.noop)
.then(function () { return result_1; });
}
else {
// No explicit result passed to end().
// Wait for ender to complete, if it throws, pass that
// error along, if it doesn't throw but an error was passed
// to end, use that error as the stream's result
p = this._readBusy.promise.then(function () {
if (_this._ended !== EOF) {
throw _this._ended;
}
});
}
this._resultDeferred.resolve(p);
}
this._readBusy = undefined;
}
// If ended, reject any pending and future writes/ends with an error
if (this._ended) {
while (this._writers.length > 0) {
// tslint:disable-next-line:no-shadowed-variable
var writer_1 = this._writers.shift();
writer_1.resolveWrite(Promise.reject(new WriteAfterEndError()));
}
return;
}
// In case we're aborting, abort all pending and future write()'s (i.e.
// not the end()'s)
if (this._abortPromise) {
while (this._writers.length > 0) {
// tslint:disable-next-line:no-shadowed-variable
var writer_2 = this._writers[0];
if (writer_2.value instanceof Eof) {
break;
}
// Reject all non-end write()'s with abort reason
util_1.swallowErrors(writer_2.value.promise);
writer_2.resolveWrite(this._abortPromise);
this._writers.shift();
}
// Fall-through to process the 'end()', if any
}
// Wait until at least one value and a reader are available
if (this._writers.length === 0 || !this._reader) {
// write(), end() and forEach() will pump us again
return;
}
var writer = this._writers[0];
// Wait until next written value is available
// (Note: when aborting, all non-end() writers will already have been
// aborted above, and an Eof is a resolved value)
if (!(writer.value instanceof Eof) && writer.value.isPending) {
writer.value.promise.then(this._pumper, this._pumper);
return;
}
// If written value resolved to a rejection, make its write() fail
if (!(writer.value instanceof Eof) && writer.value.isRejected) {
writer.resolveWrite(writer.value.promise);
this._writers.shift();
// Pump again
Promise.resolve().then(this._pumper);
return;
}
// Determine whether we should call the reader or the ender.
// Handler is always asynchronously called, and by chaining it from
// the writer's value, long stack traces are maintained.
if (writer.value instanceof Eof) {
var eof = writer.value;
// EOF, with or without error
if (this._ended || this._endPending) {
throw new Error("assertion failed");
}
this._endPending = eof;
var ender_1 = this._ender; // Ensure calling without `this`
this._ender = undefined; // Prevent calling again
// Call with end error or override with abort reason if any
var enderArg_1 = this._abortPromise ? this._abortReason : eof.error;
this._readBusy = util_1.track(Promise.resolve(eof).then(function (eofValue) { return ender_1(enderArg_1); }));
}
else {
this._readBusy = util_1.track(writer.value.promise.then(this._reader));
}
this._readBusy.promise.then(this._pumper, this._pumper);
};
return Stream;
}());
exports.Stream = Stream;
exports.default = Stream;
//# sourceMappingURL=Stream.js.map