console: do not emit error events · nodejs/node@f18e08d
@@ -2,9 +2,9 @@
2233const util = require('util');
445-function Console(stdout, stderr) {
5+function Console(stdout, stderr, ignoreErrors = true) {
66if (!(this instanceof Console)) {
7-return new Console(stdout, stderr);
7+return new Console(stdout, stderr, ignoreErrors);
88}
99if (!stdout || typeof stdout.write !== 'function') {
1010throw new TypeError('Console expects a writable stream instance');
@@ -24,8 +24,14 @@ function Console(stdout, stderr) {
2424Object.defineProperty(this, '_stdout', prop);
2525prop.value = stderr;
2626Object.defineProperty(this, '_stderr', prop);
27+prop.value = ignoreErrors;
28+Object.defineProperty(this, '_ignoreErrors', prop);
2729prop.value = new Map();
2830Object.defineProperty(this, '_times', prop);
31+prop.value = createWriteErrorHandler(stdout);
32+Object.defineProperty(this, '_stdoutErrorHandler', prop);
33+prop.value = createWriteErrorHandler(stderr);
34+Object.defineProperty(this, '_stderrErrorHandler', prop);
29353036// bind the prototype functions to this Console instance
3137var keys = Object.keys(Console.prototype);
@@ -35,20 +41,60 @@ function Console(stdout, stderr) {
3541}
3642}
374344+// Make a function that can serve as the callback passed to `stream.write()`.
45+function createWriteErrorHandler(stream) {
46+return (err) => {
47+// This conditional evaluates to true if and only if there was an error
48+// that was not already emitted (which happens when the _write callback
49+// is invoked asynchronously).
50+if (err && !stream._writableState.errorEmitted) {
51+// If there was an error, it will be emitted on `stream` as
52+// an `error` event. Adding a `once` listener will keep that error
53+// from becoming an uncaught exception, but since the handler is
54+// removed after the event, non-console.* writes won’t be affected.
55+stream.once('error', noop);
56+}
57+};
58+}
59+60+function write(ignoreErrors, stream, string, errorhandler) {
61+if (!ignoreErrors) return stream.write(string);
62+63+// There may be an error occurring synchronously (e.g. for files or TTYs
64+// on POSIX systems) or asynchronously (e.g. pipes on POSIX systems), so
65+// handle both situations.
66+try {
67+// Add and later remove a noop error handler to catch synchronous errors.
68+stream.once('error', noop);
69+70+stream.write(string, errorhandler);
71+} catch (e) {
72+// Sorry, there’s no proper way to pass along the error here.
73+} finally {
74+stream.removeListener('error', noop);
75+}
76+}
77+38783979// As of v8 5.0.71.32, the combination of rest param, template string
4080// and .apply(null, args) benchmarks consistently faster than using
4181// the spread operator when calling util.format.
4282Console.prototype.log = function log(...args) {
43-this._stdout.write(`${util.format.apply(null, args)}\n`);
83+write(this._ignoreErrors,
84+this._stdout,
85+`${util.format.apply(null, args)}\n`,
86+this._stdoutErrorHandler);
4487};
458846894790Console.prototype.info = Console.prototype.log;
489149925093Console.prototype.warn = function warn(...args) {
51-this._stderr.write(`${util.format.apply(null, args)}\n`);
94+write(this._ignoreErrors,
95+this._stderr,
96+`${util.format.apply(null, args)}\n`,
97+this._stderrErrorHandler);
5298};
539954100@@ -57,7 +103,7 @@ Console.prototype.error = Console.prototype.warn;
5710358104Console.prototype.dir = function dir(object, options) {
59105options = Object.assign({customInspect: false}, options);
60-this._stdout.write(`${util.inspect(object, options)}\n`);
106+write(this._ignoreErrors, this._stdout, `${util.inspect(object, options)}\n`);
61107};
6210863109@@ -99,3 +145,5 @@ Console.prototype.assert = function assert(expression, ...args) {
99145100146module.exports = new Console(process.stdout, process.stderr);
101147module.exports.Console = Console;
148+149+function noop() {}