|
2 | 2 |
|
3 | 3 | const util = require('util'); |
4 | 4 |
|
5 | | -function Console(stdout, stderr) { |
| 5 | +function Console(stdout, stderr, ignoreErrors = true) { |
6 | 6 | if (!(this instanceof Console)) { |
7 | | - return new Console(stdout, stderr); |
| 7 | + return new Console(stdout, stderr, ignoreErrors); |
8 | 8 | } |
9 | 9 | if (!stdout || typeof stdout.write !== 'function') { |
10 | 10 | throw new TypeError('Console expects a writable stream instance'); |
@@ -24,8 +24,14 @@ function Console(stdout, stderr) { |
24 | 24 | Object.defineProperty(this, '_stdout', prop); |
25 | 25 | prop.value = stderr; |
26 | 26 | Object.defineProperty(this, '_stderr', prop); |
| 27 | + prop.value = ignoreErrors; |
| 28 | + Object.defineProperty(this, '_ignoreErrors', prop); |
27 | 29 | prop.value = new Map(); |
28 | 30 | Object.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); |
29 | 35 |
|
30 | 36 | // bind the prototype functions to this Console instance |
31 | 37 | var keys = Object.keys(Console.prototype); |
@@ -35,20 +41,60 @@ function Console(stdout, stderr) { |
35 | 41 | } |
36 | 42 | } |
37 | 43 |
|
| 44 | +// 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 | + |
38 | 78 |
|
39 | 79 | // As of v8 5.0.71.32, the combination of rest param, template string |
40 | 80 | // and .apply(null, args) benchmarks consistently faster than using |
41 | 81 | // the spread operator when calling util.format. |
42 | 82 | Console.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); |
44 | 87 | }; |
45 | 88 |
|
46 | 89 |
|
47 | 90 | Console.prototype.info = Console.prototype.log; |
48 | 91 |
|
49 | 92 |
|
50 | 93 | Console.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); |
52 | 98 | }; |
53 | 99 |
|
54 | 100 |
|
@@ -57,7 +103,7 @@ Console.prototype.error = Console.prototype.warn; |
57 | 103 |
|
58 | 104 | Console.prototype.dir = function dir(object, options) { |
59 | 105 | options = 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`); |
61 | 107 | }; |
62 | 108 |
|
63 | 109 |
|
@@ -99,3 +145,5 @@ Console.prototype.assert = function assert(expression, ...args) { |
99 | 145 |
|
100 | 146 | module.exports = new Console(process.stdout, process.stderr); |
101 | 147 | module.exports.Console = Console; |
| 148 | + |
| 149 | +function noop() {} |