Browse Source

Rename, validate input, end/abort handlers, misc. fixes

Sven Slootweg 3 months ago
parent
commit
4a67904a85
2 changed files with 85 additions and 10 deletions
  1. 79 8
      index.js
  2. 6 2
      package.json

+ 79 - 8
index.js

@ -4,31 +4,102 @@ const Promise = require("bluebird");
4 4
const propagateAbort = require("@ppstreams/propagate-abort");
5 5
const propagatePeek = require("@ppstreams/propagate-peek");
6 6
const { isEndOfStream } = require("@ppstreams/end-of-stream-marker");
7
const { isAborted } = require("@ppstreams/aborted-marker");
8
9
const { validateOptions } = require("@validatem/core");
10
const required = require("@validatem/required");
11
const isFunction = require("@validatem/is-function");
12
const defaultTo = require("@validatem/default-to");
13
14
// FIXME: Update other stream implementations to new API
15
module.exports = function greedySinkStream(_options) {
16
	let { onResult, onEnd, onAbort } = validateOptions(arguments, {
17
		onResult: [ required, isFunction ],
18
		onEnd: [ isFunction, defaultTo.literal(defaultOnEnd) ],
19
		onAbort: [ isFunction, defaultTo.literal(defaultOnAbort) ]
20
	});
21
22
	let lastResult;
23
	let onEndCalled = false;
24
	let onEndResult;
25
	let abortHandled = false;
26
27
	function defaultOnEnd() {
28
		// We return whatever value we got last from the specified onResult callback.
29
		return lastResult;
30
	}
31
32
	function defaultOnAbort() {
33
		// no-op
34
	}
7 35
8
module.exports = function greedySinkStream(description, callback) {
9 36
	return {
10
		description: `greedy sink stream (${description})`,
37
		description: `greedy sink stream`,
11 38
		abort: propagateAbort,
12 39
		peek: propagatePeek,
13 40
		read: function produceValue_greedySinkStream(source) {
14
			let lastResult;
15
16 41
			function attemptRead() {
17 42
				return Promise.try(() => {
18 43
					return source.read();
19 44
				}).then((value) => {
20
					return callback(value);
45
					// FIXME: Document that you can pause the sink from the onResult callback, by returning a Promise that resolves when it should be resumed
46
					return onResult(value);
21 47
				}).then((result) => {
22 48
					lastResult = result;
23 49
24 50
					return attemptRead();
25 51
				}).catch(isEndOfStream, () => {
26
					/* Don't attempt to do another read, we're done. We return whatever value we got last from the specified callback. */
27
					return lastResult;
52
					/* Don't attempt to do another read, we're done.  */
53
					if (onEndCalled) {
54
						return onEndResult;
55
					} else {
56
						return Promise.try(() => {
57
							return onEnd();
58
						}).then((result) => {
59
							onEndResult = result;
60
							return result;
61
						});
62
					}
63
				}).catch((error) => !isAborted(error), (error) => {
64
					return Promise.try(() => {
65
						return source.abort(error);
66
					}).catch((abortError) => {
67
						let message = [
68
							`Tried to abort stream due to encountering an error, but the aborting itself failed`,
69
							`Original error message: ${error.message}`,
70
							`Abort failure message: ${abortError.message}`
71
						].join("\n");
72
73
						// FIXME: Make this some sort of chained error
74
						let combinedError = new Error(message);
75
						combinedError.stack = abortError.stack; // HACK
76
						throw combinedError;
77
					}).then(() => {
78
						// Pass through the original error to the user
79
						throw error;
80
					});
81
				}).catch(isAborted, (marker) => {
82
					if (abortHandled === false) {
83
						abortHandled = true;
84
85
						return Promise.try(() => {
86
							return onAbort();
87
						}).then(() => {
88
							if (marker.reason instanceof Error) {
89
								// NOTE: This ensures that the original error causing the abort is thrown exactly once
90
								throw marker.reason;
91
							} else {
92
								throw marker;
93
							}
94
						});
95
					} else {
96
						// Don't interfere, we only need special behaviour on the first occurrence
97
						throw marker;
98
					}
28 99
				});
29 100
			}
30 101
31 102
			return attemptRead();
32 103
		}
33 104
	};
34
};
105
};

+ 6 - 2
package.json

@ -1,13 +1,17 @@
1 1
{
2
  "name": "@ppstreams/greedy-sink",
2
  "name": "@ppstreams/simple-sink",
3 3
  "version": "0.1.0",
4 4
  "main": "index.js",
5
  "repository": "http://git.cryto.net/ppstreams/greedy-sink.git",
5
  "repository": "http://git.cryto.net/ppstreams/simple-sink.git",
6 6
  "author": "Sven Slootweg <admin@cryto.net>",
7 7
  "license": "WTFPL OR CC0-1.0",
8 8
  "dependencies": {
9 9
    "@ppstreams/propagate-abort": "^0.1.2",
10 10
    "@ppstreams/propagate-peek": "^0.1.0",
11
    "@validatem/core": "^0.3.11",
12
    "@validatem/default-to": "^0.1.0",
13
    "@validatem/is-function": "^0.1.0",
14
    "@validatem/required": "^0.1.1",
11 15
    "bluebird": "^3.5.4"
12 16
  }
13 17
}