Initial commit
commit
0a25a80d33
@ -0,0 +1 @@
|
||||
node_modules
|
@ -0,0 +1,120 @@
|
||||
"use strict";
|
||||
|
||||
const Promise = require("bluebird");
|
||||
const consumable = require("@joepie91/consumable");
|
||||
const pPause = require("p-pause");
|
||||
const debug = require("debug")("promistream:last-will");
|
||||
|
||||
const isEndOfStream = require("@promistream/is-end-of-stream");
|
||||
const isAborted = require("@promistream/is-aborted");
|
||||
|
||||
const { validateOptions } = require("@validatem/core");
|
||||
const isFunction = require("@validatem/is-function");
|
||||
const wrapValueAsOption = require("@validatem/wrap-value-as-option");
|
||||
const requireEither = require("@validatem/require-either");
|
||||
|
||||
let NoValue = Symbol();
|
||||
|
||||
// MARKER: Test this in batchOn, then finalize the refactoring
|
||||
|
||||
// FIXME: Make NoValue its own reusable promistream package? For use as a marker in different types of streams which need to deal with multiple possibly-matching handlers, or eg. map-filter
|
||||
// FIXME: Re-understand and refactor the logic, finish packaging it up
|
||||
module.exports = function lastWill() {
|
||||
let { onEndOfStream, onAborted, onAllEnds } = validateOptions(arguments, [
|
||||
wrapValueAsOption("onEndOfStream"), {
|
||||
onEndOfStream: [ isFunction ],
|
||||
onAborted: [ isFunction ],
|
||||
onAllEnds: [ isFunction ],
|
||||
},
|
||||
requireEither([ "onEndOfStream", "onAborted", "onAllEnds" ], { allowMultiple: true })
|
||||
]);
|
||||
|
||||
let errorBuffer = consumable();
|
||||
let hasExecutedLastWill = false;
|
||||
|
||||
// NOTE: We use a resettable pauser to ensure that when reading in parallel mode, all concurrent reads get held up while a stream end marker is being processed, but parallelization is otherwise permitted. Think of it as a conditional `sequentialize`.
|
||||
// FIXME: Verify that this doesn't ever introduce race conditions
|
||||
let pauser = pPause();
|
||||
|
||||
function throwBufferedError() {
|
||||
// Run after all custom handlers have run out
|
||||
throw errorBuffer.consume();
|
||||
}
|
||||
|
||||
function processUntilValue(handlers) {
|
||||
return Promise.reduce(handlers, (lastResult, handler, i) => {
|
||||
if (lastResult === NoValue) {
|
||||
debug(`Calling handler ${i}`);
|
||||
return handler(errorBuffer.peek());
|
||||
} else {
|
||||
debug(`Skipping handler ${i}`);
|
||||
return lastResult;
|
||||
}
|
||||
}, NoValue);
|
||||
}
|
||||
|
||||
function handleMarker(marker, markerHandler) {
|
||||
pauser.pause();
|
||||
|
||||
return Promise.try(() => {
|
||||
let relevantHandlers = [ markerHandler, onAllEnds, throwBufferedError ]
|
||||
.filter((handler) => handler != null);
|
||||
|
||||
debug(`${relevantHandlers.length - 1} handlers to try, excluding default handler`);
|
||||
|
||||
if (hasExecutedLastWill || relevantHandlers.length === 0) {
|
||||
debug("Bailing");
|
||||
throw marker;
|
||||
} else {
|
||||
errorBuffer.set(marker);
|
||||
hasExecutedLastWill = true;
|
||||
|
||||
return processUntilValue(relevantHandlers);
|
||||
}
|
||||
}).finally(() => {
|
||||
pauser.unpause();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
_promistreamVersion: 0,
|
||||
description: `last-will handler`,
|
||||
abort: function abort_lastWill(source, reason) {
|
||||
// FIXME: propagate module
|
||||
return source.abort(reason);
|
||||
},
|
||||
peek: function peek_lastWill(source) {
|
||||
return Promise.try(() => {
|
||||
return pauser.await();
|
||||
}).then(() => {
|
||||
if (errorBuffer.isSet()) {
|
||||
/* We've reached the end of the source stream in some way; we'll have at least *some* response ready, whether it is an error or some value produced by a last-will handler */
|
||||
return true;
|
||||
} else {
|
||||
return source.peek();
|
||||
}
|
||||
});
|
||||
},
|
||||
read: function produceValue_lastWill(source) {
|
||||
return Promise.try(() => {
|
||||
return pauser.await();
|
||||
}).then(() => {
|
||||
if (errorBuffer.isSet()) {
|
||||
return throwBufferedError();
|
||||
} else {
|
||||
return Promise.try(() => {
|
||||
return source.read();
|
||||
}).catch(isEndOfStream, (err) => {
|
||||
debug("Got EndOfStream marker");
|
||||
return handleMarker(err, onEndOfStream);
|
||||
}).catch(isAborted, (err) => {
|
||||
debug("Got Aborted marker");
|
||||
return handleMarker(err, onAborted);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.NoValue = NoValue;
|
@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@promistream/last-will",
|
||||
"version": "0.1.0",
|
||||
"main": "index.js",
|
||||
"repository": "http://git.cryto.net/promistream/last-will.git",
|
||||
"author": "Sven Slootweg <admin@cryto.net>",
|
||||
"license": "WTFPL OR CC0-1.0",
|
||||
"dependencies": {
|
||||
"@joepie91/consumable": "^1.0.1",
|
||||
"@promistream/is-aborted": "^0.1.0",
|
||||
"@promistream/is-end-of-stream": "^0.1.0",
|
||||
"@validatem/core": "^0.3.15",
|
||||
"@validatem/is-function": "^0.1.0",
|
||||
"@validatem/require-either": "^0.1.0",
|
||||
"@validatem/wrap-value-as-option": "^0.1.0",
|
||||
"bluebird": "^3.7.2",
|
||||
"debug": "^4.3.1",
|
||||
"p-pause": "^0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@joepie91/eslint-config": "^1.1.0",
|
||||
"eslint": "^7.23.0"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue