Browse Source

errors: port internal/process errors to internal/errors

* Assign codes to the handful of errors reported by
  internal/process/*.js
* Include documentation for the new error codes
* Improve error messages
* Improve test coverage for process.nextTick

PR-URL: https://github.com/nodejs/node/pull/11294
Ref: https://github.com/nodejs/node/issues/11273
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Evan Lucas <evanlucas@me.com>
v6
James M Snell 8 years ago
parent
commit
e75bc87d22
  1. 100
      doc/api/errors.md
  2. 28
      lib/internal/errors.js
  3. 3
      lib/internal/process/next_tick.js
  4. 24
      lib/internal/process/stdio.js
  5. 15
      lib/internal/process/warning.js
  6. 20
      test/parallel/test-internal-errors.js
  7. 27
      test/parallel/test-process-emitwarning.js
  8. 10
      test/parallel/test-process-next-tick.js
  9. 18
      test/pseudo-tty/test-tty-stdout-end.js

100
doc/api/errors.md

@ -36,8 +36,8 @@ called.
All JavaScript errors are handled as exceptions that *immediately* generate All JavaScript errors are handled as exceptions that *immediately* generate
and throw an error using the standard JavaScript `throw` mechanism. These and throw an error using the standard JavaScript `throw` mechanism. These
are handled using the [`try / catch` construct][try-catch] provided by the JavaScript are handled using the [`try / catch` construct][try-catch] provided by the
language. JavaScript language.
```js ```js
// Throws with a ReferenceError because z is undefined // 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 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 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 unhandled exception and crash unless either: The [`domain`][domains] module is
appropriately or a handler has been registered for the used appropriately or a handler has been registered for the
[`process.on('uncaughtException')`][] event. [`process.on('uncaughtException')`][] event.
```js ```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 If set to a non-number value, or set to a negative number, stack traces will
not capture any frames. 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} * {string}
The `error.message` property is the string description of the error as set by calling `new Error(message)`. The `error.message` property is the string description of the error as set by
The `message` passed to the constructor will also appear in the first line of calling `new Error(message)`. The `message` passed to the constructor will also
the stack trace of the `Error`, however changing this property after the appear in the first line of the stack trace of the `Error`, however changing
`Error` object is created *may not* change the first line of the stack trace this property after the `Error` object is created *may not* change the first
(for example, when `error.stack` is read before this property is changed). line of the stack trace (for example, when `error.stack` is read before this
property is changed).
```js ```js
const err = new Error('The message'); const err = new Error('The message');
@ -451,18 +459,18 @@ added properties.
* {string} * {string}
The `error.code` property is a string representing the error code, which is always The `error.code` property is a string representing the error code, which is
`E` followed by a sequence of capital letters. typically `E` followed by a sequence of capital letters.
#### error.errno #### error.errno
* {string|number} * {string|number}
The `error.errno` property is a number or a string. The `error.errno` property is a number or a string.
The number is a **negative** value which corresponds to the error code defined in The number is a **negative** value which corresponds to the error code defined
[`libuv Error handling`]. See uv-errno.h header file (`deps/uv/include/uv-errno.h` in in [`libuv Error handling`]. See uv-errno.h header file
the Node.js source tree) for details. (`deps/uv/include/uv-errno.h` in the Node.js source tree) for details. In case
In case of a string, it is the same as `error.code`. of a string, it is the same as `error.code`.
#### error.syscall #### error.syscall
@ -474,22 +482,22 @@ The `error.syscall` property is a string describing the [syscall][] that failed.
* {string} * {string}
When present (e.g. in `fs` or `child_process`), the `error.path` property is a string When present (e.g. in `fs` or `child_process`), the `error.path` property is a
containing a relevant invalid pathname. string containing a relevant invalid pathname.
#### error.address #### error.address
* {string} * {string}
When present (e.g. in `net` or `dgram`), the `error.address` property is a string When present (e.g. in `net` or `dgram`), the `error.address` property is a
describing the address to which the connection failed. string describing the address to which the connection failed.
#### error.port #### error.port
* {number} * {number}
When present (e.g. in `net` or `dgram`), the `error.port` property is a number representing When present (e.g. in `net` or `dgram`), the `error.port` property is a number
the connection's port that is not available. representing the connection's port that is not available.
### Common System Errors ### Common System Errors
@ -550,6 +558,53 @@ found [here][online].
encountered by [`http`][] or [`net`][] -- often a sign that a `socket.end()` encountered by [`http`][] or [`net`][] -- often a sign that a `socket.end()`
was not properly called. was not properly called.
<a id="nodejs-error-codes"></a>
## Node.js Error Codes
<a id="ERR_INVALID_ARG_TYPE"></a>
### 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.
<a id="ERR_INVALID_CALLBACK"></a>
### 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.
<a id="ERR_STDERR_CLOSE"></a>
### 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.
<a id="ERR_STDOUT_CLOSE"></a>
### 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.
<a id="ERR_UNKNOWN_STDIN_TYPE"></a>
### 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.
<a id="ERR_UNKNOWN_STREAM_TYPE"></a>
### 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.readdir`]: fs.html#fs_fs_readdir_path_options_callback
[`fs.readFileSync`]: fs.html#fs_fs_readfilesync_file_options [`fs.readFileSync`]: fs.html#fs_fs_readfilesync_file_options
[`fs.unlink`]: fs.html#fs_fs_unlink_path_callback [`fs.unlink`]: fs.html#fs_fs_unlink_path_callback
@ -562,6 +617,7 @@ found [here][online].
[domains]: domain.html [domains]: domain.html
[event emitter-based]: events.html#events_class_eventemitter [event emitter-based]: events.html#events_class_eventemitter
[file descriptors]: https://en.wikipedia.org/wiki/File_descriptor [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 [online]: http://man7.org/linux/man-pages/man3/errno.3.html
[stream-based]: stream.html [stream-based]: stream.html
[syscall]: http://man7.org/linux/man-pages/man2/syscall.2.html [syscall]: http://man7.org/linux/man-pages/man2/syscall.2.html

28
lib/internal/errors.js

@ -80,4 +80,32 @@ module.exports = exports = {
// //
// Note: Please try to keep these in alphabetical order // Note: Please try to keep these in alphabetical order
E('ERR_ASSERTION', (msg) => msg); 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... // 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;
}

3
lib/internal/process/next_tick.js

@ -10,6 +10,7 @@ exports.setup = setupNextTick;
function setupNextTick() { function setupNextTick() {
const promises = require('internal/process/promises'); const promises = require('internal/process/promises');
const errors = require('internal/errors');
const emitPendingUnhandledRejections = promises.setup(scheduleMicrotasks); const emitPendingUnhandledRejections = promises.setup(scheduleMicrotasks);
var nextTickQueue = []; var nextTickQueue = [];
var microtasksScheduled = false; var microtasksScheduled = false;
@ -139,7 +140,7 @@ function setupNextTick() {
function nextTick(callback) { function nextTick(callback) {
if (typeof callback !== 'function') 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. // on the way out, don't bother. it won't get fired anyway.
if (process._exiting) if (process._exiting)
return; return;

24
lib/internal/process/stdio.js

@ -2,14 +2,25 @@
exports.setup = setupStdio; exports.setup = setupStdio;
var errors;
function lazyErrors() {
if (!errors)
errors = require('internal/errors');
return errors;
}
function setupStdio() { function setupStdio() {
var stdin, stdout, stderr; var stdin;
var stdout;
var stderr;
function getStdout() { function getStdout() {
if (stdout) return stdout; if (stdout) return stdout;
stdout = createWritableStdioStream(1); stdout = createWritableStdioStream(1);
stdout.destroy = stdout.destroySoon = function(er) { 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); stdout.emit('error', er);
}; };
if (stdout.isTTY) { if (stdout.isTTY) {
@ -22,7 +33,8 @@ function setupStdio() {
if (stderr) return stderr; if (stderr) return stderr;
stderr = createWritableStdioStream(2); stderr = createWritableStdioStream(2);
stderr.destroy = stderr.destroySoon = function(er) { 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); stderr.emit('error', er);
}; };
if (stderr.isTTY) { if (stderr.isTTY) {
@ -79,7 +91,8 @@ function setupStdio() {
default: default:
// Probably an error on in uv_guess_handle() // 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. // For supporting legacy API we put the FD here.
@ -163,7 +176,8 @@ function createWritableStdioStream(fd) {
default: default:
// Probably an error on in uv_guess_handle() // 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. // For supporting legacy API we put the FD here.

15
lib/internal/process/warning.js

@ -5,11 +5,18 @@ const prefix = `(${process.release.name}:${process.pid}) `;
exports.setup = setupProcessWarnings; exports.setup = setupProcessWarnings;
var errors;
var fs; var fs;
var cachedFd; var cachedFd;
var acquiringFd = false; var acquiringFd = false;
function nop() {} function nop() {}
function lazyErrors() {
if (!errors)
errors = require('internal/errors');
return errors;
}
function lazyFs() { function lazyFs() {
if (!fs) if (!fs)
fs = require('fs'); fs = require('fs');
@ -105,6 +112,7 @@ function setupProcessWarnings() {
// process.emitWarning(error) // process.emitWarning(error)
// process.emitWarning(str[, type[, code]][, ctor]) // process.emitWarning(str[, type[, code]][, ctor])
process.emitWarning = function(warning, type, code, ctor) { process.emitWarning = function(warning, type, code, ctor) {
const errors = lazyErrors();
if (typeof type === 'function') { if (typeof type === 'function') {
ctor = type; ctor = type;
code = undefined; code = undefined;
@ -115,9 +123,9 @@ function setupProcessWarnings() {
code = undefined; code = undefined;
} }
if (code !== undefined && typeof code !== 'string') 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') 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') { if (warning === undefined || typeof warning === 'string') {
warning = new Error(warning); warning = new Error(warning);
warning.name = String(type || 'Warning'); warning.name = String(type || 'Warning');
@ -125,7 +133,8 @@ function setupProcessWarnings() {
Error.captureStackTrace(warning, ctor || process.emitWarning); Error.captureStackTrace(warning, ctor || process.emitWarning);
} }
if (!(warning instanceof Error)) { 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 (warning.name === 'DeprecationWarning') {
if (process.noDeprecation) if (process.noDeprecation)

20
test/parallel/test-internal-errors.js

@ -130,3 +130,23 @@ assert.throws(
() => errors.E('TEST_ERROR_USED_SYMBOL'), () => errors.E('TEST_ERROR_USED_SYMBOL'),
/^AssertionError: Error symbol: TEST_ERROR_USED_SYMBOL was already used\.$/ /^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');

27
test/parallel/test-process-emitwarning.js

@ -39,16 +39,19 @@ warningThrowToString.toString = function() {
}; };
process.emitWarning(warningThrowToString); process.emitWarning(warningThrowToString);
const expectedError =
common.expectsError({code: 'ERR_INVALID_ARG_TYPE', type: TypeError});
// TypeError is thrown on invalid input // TypeError is thrown on invalid input
assert.throws(() => process.emitWarning(1), TypeError); assert.throws(() => process.emitWarning(1), expectedError);
assert.throws(() => process.emitWarning({}), TypeError); assert.throws(() => process.emitWarning({}), expectedError);
assert.throws(() => process.emitWarning(true), TypeError); assert.throws(() => process.emitWarning(true), expectedError);
assert.throws(() => process.emitWarning([]), TypeError); assert.throws(() => process.emitWarning([]), expectedError);
assert.throws(() => process.emitWarning('', {}), TypeError); assert.throws(() => process.emitWarning('', {}), expectedError);
assert.throws(() => process.emitWarning('', '', {}), TypeError); assert.throws(() => process.emitWarning('', '', {}), expectedError);
assert.throws(() => process.emitWarning('', 1), TypeError); assert.throws(() => process.emitWarning('', 1), expectedError);
assert.throws(() => process.emitWarning('', '', 1), TypeError); assert.throws(() => process.emitWarning('', '', 1), expectedError);
assert.throws(() => process.emitWarning('', true), TypeError); assert.throws(() => process.emitWarning('', true), expectedError);
assert.throws(() => process.emitWarning('', '', true), TypeError); assert.throws(() => process.emitWarning('', '', true), expectedError);
assert.throws(() => process.emitWarning('', []), TypeError); assert.throws(() => process.emitWarning('', []), expectedError);
assert.throws(() => process.emitWarning('', '', []), TypeError); assert.throws(() => process.emitWarning('', '', []), expectedError);

10
test/parallel/test-process-next-tick.js

@ -21,6 +21,7 @@
'use strict'; 'use strict';
const common = require('../common'); const common = require('../common');
const assert = require('assert');
const N = 2; const N = 2;
function cb() { function cb() {
@ -36,3 +37,12 @@ process.on('uncaughtException', common.mustCall(N));
process.on('exit', function() { process.on('exit', function() {
process.removeAllListeners('uncaughtException'); 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'
}));
});

18
test/pseudo-tty/test-tty-stdout-end.js

@ -1,14 +1,10 @@
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const shouldThrow = function() { assert.throws(() => process.stdout.end(),
process.stdout.end(); common.expectsError({
}; code: 'ERR_STDOUT_CLOSE',
type: Error,
const validateError = function(e) { message: 'process.stdout cannot be closed'
return e instanceof Error && }));
e.message === 'process.stdout cannot be closed.';
};
assert.throws(shouldThrow, validateError);

Loading…
Cancel
Save