Node.js 10: Important Changes

The recent release of the node.js is a major milestone in its development. It contains many changes in the library, bugfixes and updated v8 engine. the complete changelog is here. This is a review of new features. It also inclues changes that have impact on the backward compatibility.
- Assert
- Buffer
- Child Process
- Console
- Crypto
- Dependencies
- EventEmitter
- File System
- HTTP
- Net
- Perf_hooks
- Process
- REPL
- Streams
- Trace Events
- URL
- Util
Calling assert.fail()
with more than one argument is deprecated.
Effect on compatibility: low
From now on calling assert.fail
with more than one argument shows the deprecation warning (node:66308) [DEP0094] DeprecationWarning: assert.fail() with more than one argument is deprecated. Please use assert.strictEqual() instead or only pass a message.
> require('assert').fail(1, 2);
AssertionError [ERR_ASSERTION]: 1 != 2
> (node:66842) [DEP0094] DeprecationWarning: assert.fail() with
more than one argument is deprecated. Please use assert.strictEqual()
instead or only pass a message.
It definitely makes assert.fail
more consistent with other frameworks (see NUnit's Assert.Fail).
Calling assert.ok()
with no arguments will now throw.
Effect on compatibility: low
When assert.ok()
is called without arguments, it throws AssertionError [ERR_ASSERTION]: No value argument passed to assert.ok()
.
> require('assert').ok();
AssertionError [ERR_ASSERTION]: No value argument passed to `assert.ok()`
assert.ok
does not really have any meaning without argument and most likely indicate either incomplete assertion or wrong assumption on how it works. But test runner actually may catch this exception and consider test successful. NUnit's Assert.Pass is for similar reason, for example.
In the previous version of the node.js it throws as well, but with different error message:
9.7.1> require('assert').ok();
AssertionError [ERR_ASSERTION]: undefined == true
Calling assert.ifError()
will now throw with any argument other than undefined or null.
Effect on compatibility: moderate
assert.ifError
is useful when asserting error in callbacks.
> require('fs').open('asdf', 'r', require('assert').ifError);
undefined
> AssertionError [ERR_ASSERTION]: ifError got unwanted exception:
ENOENT: no such file or directory, open 'asdf'
at FSReqWrap.oncomplete (fs.js:136:20)
Previously assert.isError(e)
throws only when e
is of Error
type. Now it throws for everything except undefined
and null
.
9.7.1> require('assert').ifError(1);
undefined
10.0.0> require('assert').ifError(1);
AssertionError [ERR_ASSERTION]: ifError got unwanted exception: 1
That's a quite significant change. Some tests may fail.
The assert.rejects()
and assert.doesNotReject()
methods have been added.
Effect on compatibility: none
These two are to check whether a Promise rejects or not.
> require('assert').rejects(Promise.resolve());
...
> (node:66842) UnhandledPromiseRejectionWarning:
AssertionError [ERR_ASSERTION]: Missing expected rejection.
> require('assert').doesNotReject(Promise.reject());
...
> (node:66842) UnhandledPromiseRejectionWarning:
AssertionError [ERR_ASSERTION]: Got unwanted rejection.
Actual message: "undefined"
It worth take a look on the docs for the assert.reject
and assert.doesNotReject
.
Effect on compatibility: low
This warning targets thus developers, who creating scripts. Moduled included from the node_modules
folder should not produce the warning.
> new Buffer([]);
<Buffer >
> (node:67301) [DEP0005] DeprecationWarning: Buffer() is
deprecated due to security and usability issues. Please use
the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from()
methods instead.
The main reason for this change (as I understood it) is that using one single constructor for initialising makes it too complicated, therefore a) error-prone; b) complicated in both describing and usage.
A bit more on the reasoning behind this change is in the doc.
Buffer.isEncoding()
now returns undefined for falsy values, including an empty string.
Effect on compatibility: moderate
It rather looks like a regression.
9.7.1> Buffer.isEncoding('');
true
10.0.0> Buffer.isEncoding('');
false
Buffer.fill()
will throw if an attempt is made to fill with an empty Buffer
.
Effect on compatibility: low
Any of the following code lines makes older version of the node runs forever, make sure that you do not have anything like this in your production code:
9.7.1> Buffer.alloc(1).fill(Buffer.alloc(0));
9.7.1> Buffer.alloc(1).fill(new Buffer(0));
9.7.1> Buffer.alloc(1).fill(new Buffer([]));
Although filling with zeroes might be an option in this case, the team has decided consider it illegal operation and throw an error.
10.0.0> Buffer.alloc(1).fill(Buffer.alloc(0));
TypeError [ERR_INVALID_ARG_VALUE]: The argument 'value' is invalid.
Received <Buffer >
at _fill (buffer.js:890:13)
at Buffer.fill (buffer.js:828:10)
Undefined properties of env
are ignored.
Effect on compatibility: major
Previously the environment variables with undefined values in env
are serialised to the string 'undefined'. It was a bit controversial and now such variables are ignored. It may cause behaviour change of the called program.
9.7.1> require('child_process').exec('echo $MAGIC', { env : { MAGIC : undefined } }, console.log);0;
null 'undefined\n' ''
10.0.0> require('child_process').exec('echo $MAGIC', { env : { MAGIC : undefined } }, console.log);0;
null '\n' ''
The console.table()
method has been added.
Effect on compatibility: none
I belive that need for this feature is driven by the console.table method from WebAPI. The behaviour is similar: the good-looking table that makes easier to analyse tabular data.
> console.table({ firstName : "John", lastName : "Smith" });
┌───────────┬─────────┐
│ (index) │ Values │
├───────────┼─────────┤
│ firstName │ 'John' │
│ lastName │ 'Smith' │
└───────────┴─────────┘
Unfortunately, it does not autotrim values so make sure that there is enough room in the console.
> console.table(['1'.repeat(40)])
┌─────────┬─────────────────────────────────────────
───┐
│ (index) │ Values
│
├─────────┼─────────────────────────────────────────
───┤
│ 0 │ '111111111111111111111111111111111111111
1' │
└─────────┴─────────────────────────────────────────
───┘
The crypto.createCipher()
and crypto.createDecipher()
methods have been deprecated.
Effect on compatibility: none
Instead of the crypto.createCipher()
and crypto.createDecipher()
it is recommended to use the crypto.createCipheriv()
and crypto.createDecipheriv()
methods.
The problem with auto-generated initialisation vector is that reusing this vector leads to the same encrypted sequence of bytes. Ideally, iv should be different even when the code and data are the same.
These deprecations are on the documentation level only (no messages will be printed to stdout or stderr).
The crypto.DEFAULT_ENCODING
property has been deprecated.
Effect on compatibility: low
Using global variables is a bad practice. No doubt that relying on this property complicates reusability of the libraries. Changing it may lead to the number of issues that is very hard to detect.
> crypto.DEFAULT_ENCODING;
'buffer'
> (node:69516) [DEP0091] DeprecationWarning:
crypto.DEFAULT_ENCODING is deprecated.
The ECDH.convertKey()
method has been added.
Effect on compatibility: none
New method for converting the Elliptic Curve Diffie-Hellman public key from one format to another. Might be useful if you are into cryptography.
The crypto.fips
property has been deprecated.
Effect on compatibility: low
When crypto.fips
is true then the a FIPS compliant crypto provider is currently in use. Usages of this property can be replaced with crypto.getFips()
and crypto.setFips()
.
Although this property is rarely in use, such kind of deprecation is an example of what should be avoided to support ESM modules.
Effect on compatibility: moderate
V8 JavaScript engine was updated to 6.6.346.23 from 6.5.254.43.
The list of V8 changes can be found here.
In brief, there are some changes in the language:
9.7.1> (function /* hi */ foo() {}).toString();
'function foo() {}'
10.0.0> (function /* hi */ foo() {}).toString();
'function /* hi */ foo() {}'
9.7.1> try { console.log('hi'); } catch { }
try { console.log(); } catch { }
^
SyntaxError: Unexpected token {
10.0.0> try { console.log('hi'); } catch { }
hi
OpenSSL has been updated to 1.1.0h.
Effect on compatibility: none
There are some changes and security vulnerabilities fixed. See detailed changelog for OpenSSL 1.1.0h here.
The EventEmitter.prototype.off()
method has been added.
Effect on compatibility: none
The new method off()
is just an alias for removeListener()
and presumably was added to complete the method on()
, which adds new listener to the event emitter.
> const e = new (require('events').EventEmitter)();
> const h = () => console.log('event handler');
> e.on('evt', h).emit('evt');
event handler
true
> e.off('evt', h).emit('evt');
false
Effect on compatibility: none
Finally, major step towards native support for promises in node.js. Now the fs methods can be called with await:
> (async () => console.log(await require('fs/promises').readdir('.')))();
...
(node:70521) ExperimentalWarning: The fs/promises API is experimental
[ '.DS_Store',
'.Trash',
'.bash_history',
'.bash_profile',
'.bash_sessions',
... ]
However, it should be used with caution as the API is experimental so it may be changed or removed.
More details on it here.
Invalid path errors are now thrown synchronously.
Effect on compatibility: low
Differently from the previous version of node.js, it is possible to catch the exception of the filesystem functions when the path is incorrect. Also path check is performed a bit differently.
9.7.1> try { fs.readdir('\u0000'); } catch (e) {}
Process crashed with: Error: ENOENT: no such file or
directory, scandir ''
9.7.1> fs.readdir('\u0000', () => { });
undefined
10.0.0> fs.readdir('\u0000', () => { });
TypeError [ERR_INVALID_ARG_VALUE]: The argument 'path'
must be a string or Uint8Array without null bytes. ...
10.0.0> try { fs.readdir('\u0000', () => { }); } catch {}
undefined
The fs.readFile()
method now partitions reads.
Effect on compatibility: very low
This is an important performance improvement. The readFile()
method was modified to read files in chunks, giving the place for another callbacks.
Processing of HTTP Status codes 100, 102-199 has been improved.
Effect on compatibility: low
The HTTP statuses 100, 102-199 are now processed according to RFCs. New event 'information' has been added to the http.ClientRequest. This event is emitted when the server sends a 1xx response (excluding 101 Upgrade).
*Multi-byte characters in URL paths are now forbidden.
Effect on compatibility: major
URL path is a part of the URL after the domain name and before the query parameters. For example, in the URL https://alexatnet.com/node-js-10-important-changes/
the path is '/node-js-10-important-changes/'.
Previously the http.request()
method strips the higher bytes from multi-byte characters when generating the request.
9.7.1> http.request({host: "example.com", port: "80", path: "/N"});
ClientRequest { ... }
10.0.0> http.request({host: "example.com", port: "80", path: "/N"});
TypeError [ERR_UNESCAPED_CHARACTERS]: Request path contains
unescaped characters
The 'close' event will be emitted after 'end'.
Effect on compatibility: low
This change fixes the order of events in Socket. In previous version end
may come after close
, which is incorrect. In the new version the end
always goes before close.
The PerformanceObserver
class is now an AsyncResource
.
Effect on compatibility: none
The PerformanceOserver class observes new entries in the performance timeline. Making it AsyncResource allows to use async_hooks to monitor it.
This example shows how to use async_hooks and PerformanceOserver: Measuring the duration of async operations.
Trace events are now emitted for performance events.
Effect on compatibility: none
There are two new trace categories to track: node.perf.usertiming
and node.perf.timerify
. See more on the tracing here.
The performance API has been simplified.
Effect on compatibility: low
The methods performance.getEntries*()
and performance.clear*()
are removed in the performance API. As this API is experimental, it is expected that it might be changed.
*Performance milestone marks will be emitted as trace events. *
Effect on compatibility: none
Now it is possible to track the process of node.js bootstrapping. The new category node.bootstrap
was added with the following names: nodeStart, v8Start, loopStart, loopExit, bootstrapComplete, thirdPartyMainStart, thirdPartyMainEnd, clusterSetupStart, clusterSetupEnd, moduleLoadStart, moduleLoadEnd, preloadModulesLoadStart, preloadModulesLoadEnd.
Using non-string values for process.env is deprecated.
Effect on compatibility: none
When setting a property of the process.env
, it gets converted to string. It happens for the undefined and null values as well.
> process.env.TEST = undefined;
1
> console.log(typeof process.env.TEST, process.env.TEST);
string undefined
This behaviour is deprecated, but in docs only.
Effect on compatibility: none
If node is started with --experimental-repl-await
flag, it adds experimental support for top-level await
:
$ node --experimental-repl-await
> await Promise.resolve(1);
1
Without this flat attempt to use await on the top level causes the syntax error:
$ node
> await Promise.resolve(1);
await Promise.resolve(1);
^^^^^
SyntaxError: await is only valid in async function
Proxy objects are shown as Proxy objects when inspected.
Effect on compatibility: none
In the previous version of the node the process crashes with the error when the proxy method throws:
9.7.1> new Proxy({}, { get(t, k, r) { throw new Error(); } });
Process crashed with: Error
at Object.get (evalmachine.<anonymous>:1:37)
...
With the new version the inpected object is displayed as proxy:
10.0.0> new Proxy({}, {get(t, k, r) { throw new Error(); }});
Proxy [ {}, { get: [Function: get] } ]
The 'readable' event is now always deferred with nextTick.
Effect on compatibility: low
Consider the example when the custom readable stream in its _read
method asynchronously pushes several times (based on this example):
class TestStream extends require('stream').Readable {
constructor(options) { super(options); }
_read (n) { setTimeout(() => { this.push('x'); this.push('x'); }, 1000); }
}
new TestStream().pipe(process.stdout);
In the previous node version every push will immediately call _read
, adding more handlers. In the new version the push schedules _read
to the next tick and call it only once.
The pipeline()
method has been added.
Effect on compatibility: none
The pipeline()
method forwards the data and errors through the chain of streams, calling callback when they are complete.
const pipeline = require('util').promisify(require('stream').pipleline);
await pipeline(
... input stream such as fs.createReadStream() ...,
... encode, zip, or other streams transformations ...,
...,
... output stream such as fs.createWriteStream() or HTTP response ...)
Experimental support for async for-await has been added to stream.Readable
.
Effect on compatibility: none
It is now possible to read the content of the readable stream by using for ... of
loop. Create the test.js
file with the content
(async () => {
for await (const chunk of require('fs').createReadStream('test.js')) {
process.stdout.write(chunk);
}
})();
and run with node test.js
:
$ node test.js
(node:73267) ExperimentalWarning: Readable[Symbol.asyncIterator]
is an experimental feature. This feature could change at any time
(async () => { ... skipped ... })();
A new trace_events
top-level module allows trace event categories to be enabled/disabled at runtime.
Effect on compatibility: none
This module controls the traced event categories at runtime. The entire module is new and added in node 10. See docs here.
The WHATWG URL API is now a global.
Effect on compatibility: none
The URL anc URLSearchParams classes are now global, just like in the browsers.
> new URL('http://alexatnet.com/')
URL {
href: 'http://alexatnet.com/',
...
}
util.types.is[...]
type checks have been added.
There is a number of deprecated methods (since 4.0.0) in the util module such as isArray
, isString
, etc. But checking for the types is so useful so similar set of methods is added again, now as util.types.is[...]
methods. The complete list is: isAnyArrayBuffer, isArgumentsObject, isArrayBuffer, isAsyncFunction, isBooleanObject, isDataView, isDate, isExternal, isFloat32Array, isFloat64Array, isGeneratorFunction, isGeneratorObject, isInt8Array, isInt16Array, isInt32Array, isMap, isMapIterator, isNativeError, isNumberObject, isPromise, isProxy, isRegExp, isSet, isSetIterator, isSharedArrayBuffer, isStringObject, isSymbolObject, isTypedArray, isUint8Array, isUint8ClampedArray, isUint16Array, isUint32Array, isWeakMap, isWeakSet, isWebAssemblyCompiledModule.
> (function () { return require('util').types.isArgumentsObject(arguments); })();
true
Support for bigint formatting has been added to util.inspect()
.
To test this feature the flag --harmony-bigint
should be present in node's command line arguments. util.inspect()
will then output the bigints correctly:
> require('util').inspect(12345678901234567890n);
'12345678901234567890n'