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