node/lib/internal/streams/end-of-stream.js
Ruben Bridgewater 36468ca928
lib: require a callback for end-of-stream
Make the callback mandatory as mostly done in all other Node.js
callback APIs so users explicitly have to decide what to do in error
cases.

This also documents the options for `Stream.finished()`.

When originally implemented it was missed that Stream.finished() has
an optional options object.

PR-URL: https://github.com/nodejs/node/pull/21058
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Mathias Buus <mathiasbuus@gmail.com>
2018-08-22 01:00:27 +02:00

105 lines
2.7 KiB
JavaScript

// Ported from https://github.com/mafintosh/end-of-stream with
// permission from the author, Mathias Buus (@mafintosh).
'use strict';
const {
ERR_INVALID_ARG_TYPE,
ERR_STREAM_PREMATURE_CLOSE
} = require('internal/errors').codes;
function isRequest(stream) {
return stream.setHeader && typeof stream.abort === 'function';
}
function once(callback) {
let called = false;
return function(err) {
if (called) return;
called = true;
callback.call(this, err);
};
}
function eos(stream, opts, callback) {
if (arguments.length === 2) {
callback = opts;
opts = {};
} else if (opts == null) {
opts = {};
} else if (typeof opts !== 'object') {
throw new ERR_INVALID_ARG_TYPE('opts', 'object', opts);
}
if (typeof callback !== 'function') {
throw new ERR_INVALID_ARG_TYPE('callback', 'function', callback);
}
callback = once(callback);
const ws = stream._writableState;
const rs = stream._readableState;
let readable = opts.readable || (opts.readable !== false && stream.readable);
let writable = opts.writable || (opts.writable !== false && stream.writable);
const onlegacyfinish = () => {
if (!stream.writable) onfinish();
};
const onfinish = () => {
writable = false;
if (!readable) callback.call(stream);
};
const onend = () => {
readable = false;
if (!writable) callback.call(stream);
};
const onerror = (err) => {
callback.call(stream, err);
};
const onclose = () => {
if (readable && !(rs && rs.ended)) {
return callback.call(stream, new ERR_STREAM_PREMATURE_CLOSE());
}
if (writable && !(ws && ws.ended)) {
return callback.call(stream, new ERR_STREAM_PREMATURE_CLOSE());
}
};
const onrequest = () => {
stream.req.on('finish', onfinish);
};
if (isRequest(stream)) {
stream.on('complete', onfinish);
stream.on('abort', onclose);
if (stream.req) onrequest();
else stream.on('request', onrequest);
} else if (writable && !ws) { // legacy streams
stream.on('end', onlegacyfinish);
stream.on('close', onlegacyfinish);
}
stream.on('end', onend);
stream.on('finish', onfinish);
if (opts.error !== false) stream.on('error', onerror);
stream.on('close', onclose);
return function() {
stream.removeListener('complete', onfinish);
stream.removeListener('abort', onclose);
stream.removeListener('request', onrequest);
if (stream.req) stream.req.removeListener('finish', onfinish);
stream.removeListener('end', onlegacyfinish);
stream.removeListener('close', onlegacyfinish);
stream.removeListener('finish', onfinish);
stream.removeListener('end', onend);
stream.removeListener('error', onerror);
stream.removeListener('close', onclose);
};
}
module.exports = eos;