Node.js 15 was released on October 20, 2020, with new features including throw on unhandled rejections, V8 8.6 language features, NPM 7, experimental support for QUIC, N-API Version 7, and refinement of the Async Local Storage APIs.
Abstract
Node.js 15 introduces several new features and improvements, including breaking changes. One of the major changes is the default mode for unhandled rejections, which has been changed to throw from warn. This means that if an unhandledRejection hook is not set, the unhandledRejection is raised as an uncaught exception. Another significant change is the update of the V8 JavaScript engine from 8.4 to 8.6, which brings new language features such as Promise.any() and AggregateError, await setTimeout and AbortController, String.prototype.replaceAll(), and logical assignment operators &&=, ||=, and ??=. Other changes include NPM 7, experimental support for QUIC, N-API Version 7, and refinement of the Async Local Storage APIs.
Opinions
The author suggests that it is best practice to write an error handler for promises, but acknowledges that there will be cases where errors are not caught.
The author recommends setting up the unhandledRejection hook to catch potential errors.
The author notes that both await setTimeout and AbortController are experimental features.
The author provides a list of other changes in Node.js 15, including NPM 7, experimental support for QUIC, N-API Version 7, and refinement of the Async Local Storage APIs.
The author concludes by stating that Node.js 15 has many new features and improvements, including breaking changes, and provides links to articles on newer releases and npm features.
What’s New in Node.js 15
Details of Node.js 15 new features, including throw on unhandled rejections and V8 8.6 language features
Image credit: Author
Node.js 15 was released October 20, 2020. It comes with a number of major features:
In a previous article, we provided instructions on using NVM (Node Version Manager) to manage Node.js and NPM versions. In our environment, we had Node.js 12.16.0 and NPM 6.14.8 installed. By running nvm install node, we installed Node.js 15.4.0 and NPM7.0.15.
We have two windows open, one is set to Node.js 12, and the other one is set to Node.js 15.
On the node12 window:
$ nvm use 12
Now using nodev12.16.0 (npm v6.14.8)
On the node15 window:
$ nvm use 15
Now using nodev15.4.0 (npm v7.0.15)
Now we’re ready to explore.
Throw On Unhandled Rejections
The unhandledRejection event is emitted whenever a promise is rejected and no error handler is attached to the promise within a turn of the event loop. Starting from Node.js 15, the default mode for unhandledRejection has been changed to throw from warn. In throw mode, if an unhandledRejection hook is not set, the unhandledRejection is raised as an uncaught exception.
Create a program so that a promise is rejected with an error message:
When you run this code on node12 window it shows a long warning message:
$ nodemyPromise.js
(node:79104) UnhandledPromiseRejectionWarning: #<Object>
(node:79104) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the nodeprocess on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:79104) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.Users that have an unhandledRejection hook should see no change in behavior, and it’s still possible to switch modes using the --unhandled-rejections=mode process flag.
Run this code on the node15 window and it throws the error, UnhandledPromiseRejection:
[UnhandledPromiseRejection: This error originated either by throwing inside of an asyncfunction without a catch block, orby rejecting a promise which was not handled with .catch(). The promise rejected with the reason "#<Object>".] {
code: 'ERR_UNHANDLED_REJECTION'
}
Add an error handler in the then clause in the below code (.catch((error) => console.log(error.error)) works too).
Now, the code runs correctly on both node12 and node15 windows:
$ node myPromise.js
The callis rejected with an error
It’s best practice to write an error handler for promises. However, there will be cases where errors are not caught. It’s a good idea to set up the unhandledRejection hook to catch potential errors.
The unhandledRejection hook works for both Node.js 12 and Node.js 15. With that set up, unhandledRejection can be handled properly.
$ node myPromise.js
reason is { error: 'The call is rejected with an error' }
promise is Promise { <rejected> { error: 'The call is rejected with an error' } }
V8 8.6 Language Features
The V8 JavaScript engine has been updated from 8.4 to 8.6. Along with performance tweaks and improvements, the V8 update also brings the following language features:
Promise.any() and AggregateError (from V8 8.5)
await setTimeout and AbortController (experimental)
String.prototype.replaceAll() (from V8 8.5)
Logical assignment operators &&=, ||=, and ??= (from V8 8.5)
Promise.any() and AggregateError
First, let’s take a look at the existing Promise.all() method.
Promise.all() takes an iterable of promises as an input and returns a single promise that resolves to an array of the results of the input promises.
The following program calls Promise.all() on two resolved promises:
The Promise.all() returned promise will resolve when all of the input’s promises have resolved, or if the input iterable contains no promises:
$ nodemyPromise.js
[
{ data: 'The data from 5000msdelay' },
{ data: 'The data from 100msdelay' }
]
The following program calls Promise.all() on two rejected promises.
Promise.all() immediately rejects any of the input promises rejecting or non-promises throwing an error, and will reject with this first rejection message or error:
$ nodemyPromise.js
{ error: 'The error from 100msdelay' }
Promise.any() is new in Node.js 15. This is the opposite of Promise.all(). It takes an iterable of promises and, as soon as one of the promises in the iterable fulfills, returns a single promise that resolves with the value from that promise.
The following program calls Promise.any() on two resolved promises:
$ nodemyPromise.js
{ data: 'The error from 100msdelay' }
The following program calls Promise.any() on two rejected promises:
If no promises in the iterable are fulfilled — i.e. all of the given promises are rejected — the returned promise is rejected with an AggregateError, a new subclass of Error that groups together individual errors.
$ nodemyPromise.js
[AggregateError: All promises were rejected]
[
{ error: 'The error from 5000msdelay' },
{ error: 'The error from 100msdelay' }
]
Await setTimeout and AbortController
In the previous examples, we used setTimeout inside the promise call. The WindowOrWorkerGlobalScope’s setTimeout uses a callback. However, timers/promises provides a promisified version of setTimeout, which can be used with async/await.
AbortController is a JavaScript object that allows us to abort one or more web requests as and when desired. We gave examples of how to use AbortController on the topic of useAsync.
Both await setTimeout and AbortController are experimental features.
String.prototype.replaceAll()
First, let’s take a look at the existing String.prototype.replace() method.
replace() returns a new string with some or all matches of a pattern replaced by a replacement. The pattern can be a string or a regular expression. The replacement can be a string or a function to be called for each match.
If the pattern is a string, only the first occurrence will be replaced.
'20+1+2+3'.replace('+', '-');
Executing the above statement will yield “20–1+2+3”.
In order to replace all ‘+' with ‘-’, a regular expression has to be used.
'20+1+2+3'.replace(/\+/g, '-');
Execute the above statement will yield “20–1-2-3”.
replaceAll() is new in Node.js 15 to avoid using the regular expression. It returns a new string with all matches of a pattern replaced by a replacement. The pattern can be a string or a regular expression and the replacement can be a string or a function to be called for each match.
With replaceAll(), we do not have to use a regular expression to replace all ‘+' with ‘-’.
'20+1+2+3'.replaceAll('+', '-');
Executing the above statement will yield “20–1-2-3”.
Logical assignment operators &&=, ||=, and ??=
A few logical assignment operators have been added to Node.js 15.
The logical AND assignment (x &&= y) operator only assigns if x is truthy. x &&= y is equivalent to x && (x = y), and it is not equivalent to x = x && y.
The logical OR assignment (x ||= y) operator only assigns if x is falsy. x ||= y is equivalent to x || (x = y), and it is not equivalent to x = x || y.
The logical nullish assignment (x ??= y) operator only assigns if x is nullish (null or undefined). x ??= y is equivalent to x ?? (x = y), and it is not equivalent to x = x ?? y.
Other Changes
Besides throw on unhandled rejections and V8 8.6 language features, Node.js 15 has the following changes:
NPM 7: There are many changes, including peer dependency automatic installation, package and yarn lock files enhancement, workspace support, and more. This is documented in another article.
QUIC: An experimental support for UDP transport protocol that’s the underlying transport protocol for HTTP/3. It has built-in security with TLS 1.3, flow control, error correction, connection migration, and multiplexing.
N-API Version 7: An API for building native Addons. Independent from the underlying JavaScript runtime and maintained as part of Node.js itself.
Refinement of the Async Local Storage APIs: Enables more sophisticated logging and analysis features for large-scale applications.
Conclusion
Node.js 15 has many new features and improvements, including breaking changes.
If you want to check out newer releases, take a look at following articles: