diff --git a/doc/api/errors.md b/doc/api/errors.md index 05758cf20b7dd7..7ee2963ae0352b 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -36,8 +36,8 @@ called. All JavaScript errors are handled as exceptions that *immediately* generate and throw an error using the standard JavaScript `throw` mechanism. These -are handled using the [`try / catch` construct][try-catch] provided by the JavaScript -language. +are handled using the [`try / catch` construct][try-catch] provided by the +JavaScript language. ```js // Throws with a ReferenceError because z is undefined @@ -105,8 +105,8 @@ pass or fail). For *all* `EventEmitter` objects, if an `'error'` event handler is not provided, the error will be thrown, causing the Node.js process to report an -unhandled exception and crash unless either: The [`domain`][domains] module is used -appropriately or a handler has been registered for the +unhandled exception and crash unless either: The [`domain`][domains] module is +used appropriately or a handler has been registered for the [`process.on('uncaughtException')`][] event. ```js @@ -255,15 +255,23 @@ will affect any stack trace captured *after* the value has been changed. If set to a non-number value, or set to a negative number, stack traces will not capture any frames. -### error.message +#### error.code + +* {string} + +The `error.code` property is a string label that identifies the kind of error. +See [Node.js Error Codes][] for details about specific codes. + +#### error.message * {string} -The `error.message` property is the string description of the error as set by calling `new Error(message)`. -The `message` passed to the constructor will also appear in the first line of -the stack trace of the `Error`, however changing this property after the -`Error` object is created *may not* change the first line of the stack trace -(for example, when `error.stack` is read before this property is changed). +The `error.message` property is the string description of the error as set by +calling `new Error(message)`. The `message` passed to the constructor will also +appear in the first line of the stack trace of the `Error`, however changing +this property after the `Error` object is created *may not* change the first +line of the stack trace (for example, when `error.stack` is read before this +property is changed). ```js const err = new Error('The message'); @@ -451,18 +459,18 @@ added properties. * {string} -The `error.code` property is a string representing the error code, which is always -`E` followed by a sequence of capital letters. +The `error.code` property is a string representing the error code, which is +typically `E` followed by a sequence of capital letters. #### error.errno * {string|number} The `error.errno` property is a number or a string. -The number is a **negative** value which corresponds to the error code defined in -[`libuv Error handling`]. See uv-errno.h header file (`deps/uv/include/uv-errno.h` in -the Node.js source tree) for details. -In case of a string, it is the same as `error.code`. +The number is a **negative** value which corresponds to the error code defined +in [`libuv Error handling`]. See uv-errno.h header file +(`deps/uv/include/uv-errno.h` in the Node.js source tree) for details. In case +of a string, it is the same as `error.code`. #### error.syscall @@ -474,22 +482,22 @@ The `error.syscall` property is a string describing the [syscall][] that failed. * {string} -When present (e.g. in `fs` or `child_process`), the `error.path` property is a string -containing a relevant invalid pathname. +When present (e.g. in `fs` or `child_process`), the `error.path` property is a +string containing a relevant invalid pathname. #### error.address * {string} -When present (e.g. in `net` or `dgram`), the `error.address` property is a string -describing the address to which the connection failed. +When present (e.g. in `net` or `dgram`), the `error.address` property is a +string describing the address to which the connection failed. #### error.port * {number} -When present (e.g. in `net` or `dgram`), the `error.port` property is a number representing -the connection's port that is not available. +When present (e.g. in `net` or `dgram`), the `error.port` property is a number +representing the connection's port that is not available. ### Common System Errors @@ -550,6 +558,53 @@ found [here][online]. encountered by [`http`][] or [`net`][] -- often a sign that a `socket.end()` was not properly called. + +## Node.js Error Codes + + +### ERR_INVALID_ARG_TYPE + +The `'ERR_INVALID_ARG_TYPE'` error code is used generically to identify that +an argument of the wrong type has been passed to a Node.js API. + + +### ERR_INVALID_CALLBACK + +The `'ERR_INVALID_CALLBACK'` error code is used generically to identify that +a callback function is required and has not been provided to a Node.js API. + + +### ERR_STDERR_CLOSE + +An error using the `'ERR_STDERR_CLOSE'` code is thrown specifically when an +attempt is made to close the `process.stderr` stream. By design, Node.js does +not allow `stdout` or `stderr` Streams to be closed by user code. + + +### ERR_STDOUT_CLOSE + +An error using the `'ERR_STDOUT_CLOSE'` code is thrown specifically when an +attempt is made to close the `process.stdout` stream. By design, Node.js does +not allow `stdout` or `stderr` Streams to be closed by user code. + + +### ERR_UNKNOWN_STDIN_TYPE + +An error using the `'ERR_UNKNOWN_STDIN_TYPE'` code is thrown specifically when +an attempt is made to launch a Node.js process with an unknown `stdin` file +type. Errors of this kind cannot *typically* be caused by errors in user code, +although it is not impossible. Occurrences of this error are most likely an +indication of a bug within Node.js itself. + + +### ERR_UNKNOWN_STREAM_TYPE + +An error using the `'ERR_UNKNOWN_STREAM_TYPE'` code is thrown specifically when +an attempt is made to launch a Node.js process with an unknown `stdout` or +`stderr` file type. Errors of this kind cannot *typically* be caused by errors +in user code, although it is not impossible. Occurrences of this error are most +likely an indication of a bug within Node.js itself. + [`fs.readdir`]: fs.html#fs_fs_readdir_path_options_callback [`fs.readFileSync`]: fs.html#fs_fs_readfilesync_file_options [`fs.unlink`]: fs.html#fs_fs_unlink_path_callback @@ -562,6 +617,7 @@ found [here][online]. [domains]: domain.html [event emitter-based]: events.html#events_class_eventemitter [file descriptors]: https://en.wikipedia.org/wiki/File_descriptor +[Node.js Error Codes]: #nodejs-error-codes [online]: http://man7.org/linux/man-pages/man3/errno.3.html [stream-based]: stream.html [syscall]: http://man7.org/linux/man-pages/man2/syscall.2.html diff --git a/lib/internal/errors.js b/lib/internal/errors.js index aa3bb0de1df7c6..dc56c68f34d281 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -80,4 +80,32 @@ module.exports = exports = { // // Note: Please try to keep these in alphabetical order E('ERR_ASSERTION', (msg) => msg); +E('ERR_INVALID_ARG_TYPE', invalidArgType); +E('ERR_INVALID_CALLBACK', 'callback must be a function'); +E('ERR_STDERR_CLOSE', 'process.stderr cannot be closed'); +E('ERR_STDOUT_CLOSE', 'process.stdout cannot be closed'); +E('ERR_UNKNOWN_STDIN_TYPE', 'Unknown stdin file type'); +E('ERR_UNKNOWN_STREAM_TYPE', 'Unknown stream file type'); // Add new errors from here... + +function invalidArgType(name, expected, actual) { + assert(name, 'name is required'); + assert(expected, 'expected is required'); + var msg = `The "${name}" argument must be `; + if (Array.isArray(expected)) { + var len = expected.length; + expected = expected.map((i) => String(i)); + if (len > 1) { + msg += `one of type ${expected.slice(0, len - 1).join(', ')}, or ` + + expected[len - 1]; + } else { + msg += `of type ${expected[0]}`; + } + } else { + msg += `of type ${String(expected)}`; + } + if (arguments.length >= 3) { + msg += `. Received type ${actual !== null ? typeof actual : 'null'}`; + } + return msg; +} diff --git a/lib/internal/process/next_tick.js b/lib/internal/process/next_tick.js index ad635aaf494b33..c834ffc2e39fe9 100644 --- a/lib/internal/process/next_tick.js +++ b/lib/internal/process/next_tick.js @@ -10,6 +10,7 @@ exports.setup = setupNextTick; function setupNextTick() { const promises = require('internal/process/promises'); + const errors = require('internal/errors'); const emitPendingUnhandledRejections = promises.setup(scheduleMicrotasks); var nextTickQueue = []; var microtasksScheduled = false; @@ -139,7 +140,7 @@ function setupNextTick() { function nextTick(callback) { if (typeof callback !== 'function') - throw new TypeError('callback is not a function'); + throw new errors.TypeError('ERR_INVALID_CALLBACK'); // on the way out, don't bother. it won't get fired anyway. if (process._exiting) return; diff --git a/lib/internal/process/stdio.js b/lib/internal/process/stdio.js index cf69657e40583e..b6c866e194bdbb 100644 --- a/lib/internal/process/stdio.js +++ b/lib/internal/process/stdio.js @@ -2,14 +2,25 @@ exports.setup = setupStdio; +var errors; + +function lazyErrors() { + if (!errors) + errors = require('internal/errors'); + return errors; +} + function setupStdio() { - var stdin, stdout, stderr; + var stdin; + var stdout; + var stderr; function getStdout() { if (stdout) return stdout; stdout = createWritableStdioStream(1); stdout.destroy = stdout.destroySoon = function(er) { - er = er || new Error('process.stdout cannot be closed.'); + const errors = lazyErrors(); + er = er || new errors.Error('ERR_STDOUT_CLOSE'); stdout.emit('error', er); }; if (stdout.isTTY) { @@ -22,7 +33,8 @@ function setupStdio() { if (stderr) return stderr; stderr = createWritableStdioStream(2); stderr.destroy = stderr.destroySoon = function(er) { - er = er || new Error('process.stderr cannot be closed.'); + const errors = lazyErrors(); + er = er || new errors.Error('ERR_STDERR_CLOSE'); stderr.emit('error', er); }; if (stderr.isTTY) { @@ -79,7 +91,8 @@ function setupStdio() { default: // Probably an error on in uv_guess_handle() - throw new Error('Implement me. Unknown stdin file type!'); + const errors = lazyErrors(); + throw new errors.Error('ERR_UNKNOWN_STDIN_TYPE'); } // For supporting legacy API we put the FD here. @@ -163,7 +176,8 @@ function createWritableStdioStream(fd) { default: // Probably an error on in uv_guess_handle() - throw new Error('Implement me. Unknown stream file type!'); + const errors = lazyErrors(); + throw new errors.Error('ERR_UNKNOWN_STREAM_TYPE'); } // For supporting legacy API we put the FD here. diff --git a/lib/internal/process/warning.js b/lib/internal/process/warning.js index bf487f38a417c4..86fe491e576668 100644 --- a/lib/internal/process/warning.js +++ b/lib/internal/process/warning.js @@ -5,11 +5,18 @@ const prefix = `(${process.release.name}:${process.pid}) `; exports.setup = setupProcessWarnings; +var errors; var fs; var cachedFd; var acquiringFd = false; function nop() {} +function lazyErrors() { + if (!errors) + errors = require('internal/errors'); + return errors; +} + function lazyFs() { if (!fs) fs = require('fs'); @@ -105,6 +112,7 @@ function setupProcessWarnings() { // process.emitWarning(error) // process.emitWarning(str[, type[, code]][, ctor]) process.emitWarning = function(warning, type, code, ctor) { + const errors = lazyErrors(); if (typeof type === 'function') { ctor = type; code = undefined; @@ -115,9 +123,9 @@ function setupProcessWarnings() { code = undefined; } if (code !== undefined && typeof code !== 'string') - throw new TypeError('\'code\' must be a String'); + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'code', 'string'); if (type !== undefined && typeof type !== 'string') - throw new TypeError('\'type\' must be a String'); + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'type', 'string'); if (warning === undefined || typeof warning === 'string') { warning = new Error(warning); warning.name = String(type || 'Warning'); @@ -125,7 +133,8 @@ function setupProcessWarnings() { Error.captureStackTrace(warning, ctor || process.emitWarning); } if (!(warning instanceof Error)) { - throw new TypeError('\'warning\' must be an Error object or string.'); + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', + 'warning', ['Error', 'string']); } if (warning.name === 'DeprecationWarning') { if (process.noDeprecation) diff --git a/test/parallel/test-internal-errors.js b/test/parallel/test-internal-errors.js index b43d825e6cc995..fbd627b0301dfd 100644 --- a/test/parallel/test-internal-errors.js +++ b/test/parallel/test-internal-errors.js @@ -130,3 +130,23 @@ assert.throws( () => errors.E('TEST_ERROR_USED_SYMBOL'), /^AssertionError: Error symbol: TEST_ERROR_USED_SYMBOL was already used\.$/ ); + +// // Test ERR_INVALID_ARG_TYPE +assert.strictEqual(errors.message('ERR_INVALID_ARG_TYPE', ['a', 'b']), + 'The "a" argument must be of type b'); +assert.strictEqual(errors.message('ERR_INVALID_ARG_TYPE', ['a', ['b']]), + 'The "a" argument must be of type b'); +assert.strictEqual(errors.message('ERR_INVALID_ARG_TYPE', ['a', ['b', 'c']]), + 'The "a" argument must be one of type b, or c'); +assert.strictEqual(errors.message('ERR_INVALID_ARG_TYPE', + ['a', ['b', 'c', 'd']]), + 'The "a" argument must be one of type b, c, or d'); +assert.strictEqual(errors.message('ERR_INVALID_ARG_TYPE', ['a', 'b', 'c']), + 'The "a" argument must be of type b. Received type string'); +assert.strictEqual(errors.message('ERR_INVALID_ARG_TYPE', + ['a', 'b', undefined]), + 'The "a" argument must be of type b. Received type ' + + 'undefined'); +assert.strictEqual(errors.message('ERR_INVALID_ARG_TYPE', + ['a', 'b', null]), + 'The "a" argument must be of type b. Received type null'); diff --git a/test/parallel/test-process-emitwarning.js b/test/parallel/test-process-emitwarning.js index b7491000014dc5..bebdc989a2dbff 100644 --- a/test/parallel/test-process-emitwarning.js +++ b/test/parallel/test-process-emitwarning.js @@ -39,16 +39,19 @@ warningThrowToString.toString = function() { }; process.emitWarning(warningThrowToString); +const expectedError = + common.expectsError({code: 'ERR_INVALID_ARG_TYPE', type: TypeError}); + // TypeError is thrown on invalid input -assert.throws(() => process.emitWarning(1), TypeError); -assert.throws(() => process.emitWarning({}), TypeError); -assert.throws(() => process.emitWarning(true), TypeError); -assert.throws(() => process.emitWarning([]), TypeError); -assert.throws(() => process.emitWarning('', {}), TypeError); -assert.throws(() => process.emitWarning('', '', {}), TypeError); -assert.throws(() => process.emitWarning('', 1), TypeError); -assert.throws(() => process.emitWarning('', '', 1), TypeError); -assert.throws(() => process.emitWarning('', true), TypeError); -assert.throws(() => process.emitWarning('', '', true), TypeError); -assert.throws(() => process.emitWarning('', []), TypeError); -assert.throws(() => process.emitWarning('', '', []), TypeError); +assert.throws(() => process.emitWarning(1), expectedError); +assert.throws(() => process.emitWarning({}), expectedError); +assert.throws(() => process.emitWarning(true), expectedError); +assert.throws(() => process.emitWarning([]), expectedError); +assert.throws(() => process.emitWarning('', {}), expectedError); +assert.throws(() => process.emitWarning('', '', {}), expectedError); +assert.throws(() => process.emitWarning('', 1), expectedError); +assert.throws(() => process.emitWarning('', '', 1), expectedError); +assert.throws(() => process.emitWarning('', true), expectedError); +assert.throws(() => process.emitWarning('', '', true), expectedError); +assert.throws(() => process.emitWarning('', []), expectedError); +assert.throws(() => process.emitWarning('', '', []), expectedError); diff --git a/test/parallel/test-process-next-tick.js b/test/parallel/test-process-next-tick.js index 1931320beb61fa..9cbdfc6d3b6a2d 100644 --- a/test/parallel/test-process-next-tick.js +++ b/test/parallel/test-process-next-tick.js @@ -21,6 +21,7 @@ 'use strict'; const common = require('../common'); +const assert = require('assert'); const N = 2; function cb() { @@ -36,3 +37,12 @@ process.on('uncaughtException', common.mustCall(N)); process.on('exit', function() { process.removeAllListeners('uncaughtException'); }); + +[null, 1, 'test', {}, [], Infinity, true].forEach((i) => { + assert.throws(() => process.nextTick(i), + common.expectsError({ + code: 'ERR_INVALID_CALLBACK', + type: TypeError, + message: 'callback must be a function' + })); +}); diff --git a/test/pseudo-tty/test-tty-stdout-end.js b/test/pseudo-tty/test-tty-stdout-end.js index 86a42c4035b7ae..3c91977f902cd2 100644 --- a/test/pseudo-tty/test-tty-stdout-end.js +++ b/test/pseudo-tty/test-tty-stdout-end.js @@ -1,14 +1,10 @@ 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); -const shouldThrow = function() { - process.stdout.end(); -}; - -const validateError = function(e) { - return e instanceof Error && - e.message === 'process.stdout cannot be closed.'; -}; - -assert.throws(shouldThrow, validateError); +assert.throws(() => process.stdout.end(), + common.expectsError({ + code: 'ERR_STDOUT_CLOSE', + type: Error, + message: 'process.stdout cannot be closed' + }));