What's new in Node.js 15?

We are sharing the translation of the article, which contains details about the new features of the 15th version of Node.js.


Node.js version 15 was released on October 20, 2020. Major changes include:



  • throw mode on unhandled deviations
  • V8 8.6 language features
  • NPM 7
  • experimental QUIC support
  • N-API Version 7
  • finalization of the Async Local Storage API


Let's take a closer look at what these innovations are and how they can be used.



Using NVM for Node Overview



In the previous article, we went over the instructions for using NVM (Node Version Manager) to manage versions of Node.js and NPM. We have Node.js 12.16.0 and NPM 6.14.8 installed in our environment. By running nvm install node , we have installed Node.js 15.4.0 and NPM7.0.15.



We have two windows open, one with Node.js 12 and the other with Node.js 15.



In the node12 window :



$ nvm use 12
Now using node v12.16.0 (npm v6.14.8)
      
      





In node15 window :



$ nvm use 15
Now using node v15.4.0 (npm v7.0.15)
      
      





We can now investigate this version.



Throw mode on unhandled promise rejection



The unhandledRejection event is generated every time a promise is rejected and an error handler is not attached to the promise during the event loop. As of Node.js 15, the default mode for unhandledRejection has been changed from warn to throw . In throw mode , if the unhandledRejection hook is not set, the unhandledRejection is thrown as an exception not caught by the catch method .



Create a program to rejected the promise with an error message:



function myPromise() {
  new Promise((_, reject) =>
    setTimeout(
      () =>
        reject({
          error: 'The call is rejected with an error',
        }),
      1000
    )
  ).then((data) => console.log(data.data));
}

myPromise();
      
      





When you run this code in a node12 window , a long warning message appears:



$ node myPromise.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 node process 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.
      
      





Running this code in window node15 generates an UnhandledPromiseRejection error :



$ node myPromise.js
node:internal/process/promises:227
          triggerUncaughtException(err, true /* fromPromise */);
          ^[UnhandledPromiseRejection: 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(). The promise rejected with the reason "#<Object>".] {
  code: 'ERR_UNHANDLED_REJECTION'
}
      
      





Add an error handler to the then branch in the code below ( .catch (( error ) => console.log ( error .error)) also works).



function myPromise() {
  new Promise((_, reject) =>
    setTimeout(
      () =>
        reject({
          error: 'The call is rejected with an error',
        }),
      1000
    )
  ).then(
    (data) => console.log(data.data),
    (error) => console.log(error.error)
  );
}

myPromise();
      
      





Now the code runs correctly in both windows ( node12 and node15 ):



$ node myPromise.js
The call is rejected with an error
      
      





It is recommended to write an error handler for promises. However, there may be cases where errors are not caught by the catch method. It is recommended to set up the unhandledRejection hook to detect potential errors.



function myPromise() {
  new Promise((_, reject) =>
    setTimeout(
      () =>
        reject({
          error: 'The call is rejected with an error',
        }),
      1000
    )
  ).then((data) => console.log(data.data));
}

myPromise();

process.on('unhandledRejection', (reason, promise) => {
  console.log('reason is', reason);
  console.log('promise is', promise);
  // Application specific logging, throwing an error, or other logic here
});
      
      





The unhandledRejection hook works in both Node.js 12 and Node.js 15. Once installed, the unhandledRejection is handled as needed.



$ 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 New language features



V8, JavaScript engine, updated from 8.4 to 8.6. version. In addition to various tweaks to improve performance, the new V8 has the following 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 look at the existing Promise.all () method .



Promise.all () takes an iterable from promises as input and returns a single promise, which is executed as an array of the results of the input promises.



The following program calls Promise.all () on two resolved promises:



function myPromise(delay) {
  return new Promise((resolve) =>
    setTimeout(
      () =>
        resolve({
          data: The data from ${delay} ms delay,
        }),
      delay
    )
  );
}

async function getData() {
  try {
    const data = await Promise.all([myPromise(5000), myPromise(100)]);
    console.log(data);
  } catch (error) {
    console.log(error);
  }
}

getData();

      
      





Promise.all () returns a promise that will be fulfilled when all input promises are resolved, or if the iterable contains no promises:



$ node myPromise.js
[
  { data: 'The data from 5000 ms delay' },
  { data: 'The data from 100 ms delay' }
]
      
      





The following program calls Promise.all () on two rejected promises.



function myPromise(delay) {
  return new Promise((_, reject) =>
    setTimeout(
      () =>
        reject({
          error: The error from ${delay} ms delay,
        }),
      delay
    )
  );
}

async function getData() {
  try {
    const data = await Promise.all([myPromise(5000), myPromise(100)]);
    console.log(data);
  } catch (error) {
    console.log(error);
  }
}

getData();
      
      





Promise.all () immediately rejects any rejection of the input promise or any error at the time of execution, returning a message about this error:



$ node myPromise.js
{ error: 'The error from 100 ms delay' }
      
      





Promise.any () is a new method in Node.js 15. It is the opposite of Promise.all () . Promise.any () accepts an iterable containing Promise objects. And, as soon as one of the Promises in the iterable succeeds, the method will return a single promise with the value of the fulfilled β€œpromise”.



The following program calls Promise.any () on two resolved promises:



function myPromise(delay) {
  return new Promise((resolve) =>
    setTimeout(
      () =>
        resolve({
          data: The error from ${delay} ms delay,
        }),
      delay
    )
  );
}

async function getData() {
  try {
    const data = await Promise.any([myPromise(5000), myPromise(100)]);
    console.log(data);
  } catch (error) {
    console.log(error);
    console.log(error.errors);
  }
}

getData();
      
      





Promise.any () returns the first resolved promise:



$ node myPromise.js
{ data: 'The error from 100 ms delay' }
      
      





The following program calls Promise.any () on two rejected promises:



function myPromise(delay) {
  return new Promise((_, reject) =>
    setTimeout(
      () =>
        reject({
          error: The error from ${delay} ms delay,
        }),
      delay
    )
  );
}

async function getData() {
  try {
    const data = await Promise.any([myPromise(5000), myPromise(100)]);
    console.log(data);
  } catch (error) {
    console.log(error);
    console.log(error.errors);
  }
}

getData();
      
      





If the promises in the iterable fail, i.e. all promises given are rejected, the returned promise is rejected with AggregateError , a new subclass of Error that groups individual errors together.



$ node myPromise.js
[AggregateError: All promises were rejected]
[
  { error: 'The error from 5000 ms delay' },
  { error: 'The error from 100 ms delay' }
]
      
      





Await setTimeout and AbortController



In the previous examples, we used setTimeout inside a promise call.



SetTimeout on WindowOrWorkerGlobalScope uses a callback. However, timers / promises provide a promisified version of setTimeout that can be used with async / await.



const { setTimeout } = require('timers/promises');

async function myPromise(delay) {
  await setTimeout(delay);
  return new Promise((resolve) => {
    resolve({
      data: The data from ${delay} ms delay,
    });
  });
}

async function getData() {
  try {
    const data = await Promise.any([myPromise(5000), myPromise(100)]);
    console.log(data);
  } catch (error) {
    console.log(error);
    console.log(error.errors);
  }
}

getData();
      
      





AbortController is a JavaScript object that allows you to abort one or more web requests at will. We have provided examples of using the AbortController in another article on useAsync .



Both await setTimeout and AbortController are experimental features.



String.prototype.replaceAll ()



Let's take a look at the existing String.prototype.replace () method .



replace () returns a new string with some or all of the pattern matches replaced with the replacer. The pattern can be a string or a regular expression. The placeholder can be a string or a function that is called for each match.



If the pattern is a string, only the first occurrence will be replaced.



'20+1+2+3'.replace('+', '-');
      
      





Using this operator will give β€œ20–1 + 2 + 3” .



To replace all "+" with "-", you need to use a regular expression.



'20+1+2+3'.replace(/\+/g, '-');
      
      





Using the above operator will give β€œ20-1-2-3” .



The replaceAll () method is new in Node.js 15. By using it, we don't have to use a regular expression. This method returns a new string with all pattern matches replaced with the placeholder. The pattern can be a string or a regular expression, and the placeholder can be a string or a function called for each match.



Thanks to the replaceAll () method , we don't have to use a regular expression to replace all "+" with "-".



'20+1+2+3'.replaceAll('+', '-');
      
      





Execution of this operator gives β€œ20-1-2-3” .



Logical assignment operators && =, || = and ?? =



The logical assignment operator AND ( x && = y ) performs an assignment operation only if x is true.



x && = y is equivalent to x && (x = y) , but not equivalent to x = x && y .



let x = 0;
let y = 1;

x &&= 0; // 0
x &&= 1; // 0
y &&= 1; // 1
y &&= 0; // 0
      
      





The logical assignment operator OR (x || = y) performs an assignment operation only if x is false.



x || = y is equivalent to x || (x = y) but not equivalent to x = x || at .



let x = 0;
let y = 1;

x ||= 0; // 0
x ||= 1; // 1
y ||= 1; // 1
y ||= 0; // 1
      
      





The logical assignment operator nullish (x ?? = y) performs an assignment operation only if x is NULL ( null or undefined ).



x ?? = y is equivalent to x ?? (x = y) , and not equivalent to x = x ?? at .



let x = undefined;
let y = '';

x ??= null; // null
x ??= 'a value'; // "a value"
y ??= undefined; // ""
y ??= null; // ""
      
      





Other changes



In addition to the throw mode on unhandled promise rejection and new V8 8.6 language features, Node.js 15 has the following changes:



NPM 7 : Many changes, including automatic installation of peer dependencies, improvements to packages and yarn.lock files, workspace support, etc. All this is described in this article by reference .



QUIC : Experimental support for the UDP transport layer, which is the primary protocol for HTTP / 3. QUIC includes embedded security with TLS 1.3, flow control, error correction, connection migration, and multiplexing.



N-API Version 7: API for creating custom addons. It is independent of the underlying JavaScript runtime and is supported as part of Node.js.



Async Local Storage API enhancements : Provides the ability for more modern logging and feature analysis for large-scale applications.



Conclusion



The new version of Node.js 15 has a large number of new features and improvements, including quite significant ones.



Try the new version and get ready to update projects.



Thanks for attention! I hope the article was helpful to you.



All Articles