Skip to content

Say Hello to Node.js 10.0.0

Node.js 10.0.0 brings greater stability, performance, and reliability.

Node.js 10.0.0 Highlights

It's April 2018 which means it's time for the arrival of Node.js 10.0.0, the seventh major release of the platform since the launch of the Node.js Foundation, and the seventh major release that I personally have had the honour to help shepherd out the door.

Node.js 10 focuses primarily on incremental improvements made throughout the whole platform. There are very few areas of the codebase that have not been touched by over 750 commits included in the release.

There's plenty in the release to get excited about! Here I want only to touch on the highlights. Refer to the changelog to get the full details.

Getting assertive

NearForm's own Ruben Bridgewater ( @BridgeAR on GitHub) has been working tirelessly to improve the internal implementation and developer experience of the Node.js assert module. One of the more visible changes is the introduction of a new diff view when assertion errors are thrown. The best way to describe the new capability is with an example.

Running the following code in Node.js 9.11.1:

Plain Text
assert.strictEqual({ a: 1 }, { a: 2 });

Results in the rather cryptic and unhelpful error message:

Plain Text
AssertionError [ERR_ASSERTION]: { a: 1 } === { a: 2 }

Running the same bit of code in Node.js 10.0.0 yields a much more descriptive and useful error:

Plain Text
AssertionError [ERR_ASSERTION]: Input A expected to strictly equal input B:
+ expected - actual</p><p>  {
-   a: 1
+   a: 2
  }

A similar treatment has been given to the assert.ifError() method, which throws an assertion error if the input argument is an Error object:

Plain Text
try {
  assert.ifError(new TypeError());
} catch (err) {
  console.log(err);
}

When running Node.js 9.11.1, the output is:

Plain Text
TypeError
    at repl:1:22
    at Script.runInThisContext (vm.js:65:33)
    at REPLServer.defaultEval (repl.js:248:29)
    at bound (domain.js:376:14)
    at REPLServer.runBound [as eval] (domain.js:389:12)
    at REPLServer.onLine (repl.js:496:10)
    at REPLServer.emit (events.js:185:15)
    at REPLServer.emit (domain.js:422:20)
    at REPLServer.Interface._onLine (readline.js:285:10)
    at REPLServer.Interface._line (readline.js:638:8)

Which is OK in that the original error message is preserved, but the fact that this is a failed assertion is lost.

In Node.js 10.0.0, the output becomes:

Plain Text
{ AssertionError [ERR_ASSERTION]: ifError got unwanted exception: TypeError
    at repl:1:14
    at repl:1:22
    at Script.runInThisContext (vm.js:91:20)
    at REPLServer.defaultEval (repl.js:311:29)
    at bound (domain.js:396:14)
    at REPLServer.runBound [as eval] (domain.js:409:12)
    at REPLServer.onLine (repl.js:609:10)
    at REPLServer.emit (events.js:200:15)
    at REPLServer.emit (domain.js:442:20)
    at REPLServer.Interface._onLine (readline.js:285:10)
    at REPLServer.Interface._line (readline.js:633:8)
  generatedMessage: false,
  name: 'AssertionError [ERR_ASSERTION]',
  code: 'ERR_ASSERTION',
  actual: TypeError
    at repl:1:22
    at Script.runInThisContext (vm.js:91:20)
    at REPLServer.defaultEval (repl.js:311:29)
    at bound (domain.js:396:14)
    at REPLServer.runBound [as eval] (domain.js:409:12)
    at REPLServer.onLine (repl.js:609:10)
    at REPLServer.emit (events.js:200:15)
    at REPLServer.emit (domain.js:442:20)
    at REPLServer.Interface._onLine (readline.js:285:10)
    at REPLServer.Interface._line (readline.js:633:8),
  expected: null,
  operator: 'ifError' }

Significantly more verbose but also significantly more useful.

There are several other important changes in the assert module worth exploring and playing around with including improved error message detail, promises support, and better object comparisons.

Buffers again

The venerable Node.js Buffer object is once again getting some attention. You may recall that back in Node.js 6.0.0 a number of new methods were introduced for creating Buffer objects (e.g. Buffer.from() , Buffer.alloc() , Buffer.allocUnsafe() , and so on). You may also vaguely recall something about how these were added because of security concerns around using the new Buffer() and Buffer() (without the new keyword). What may have gone unnoticed, however, is the fact that when the new methods were introduced, the old constructors were marked as deprecated in the documentation.

Come to find out, three years later, not only are Node.js developers still using the old new Buffer() and Buffer() constructors, there's even more code published to the ecosystem today using the deprecated versions than there were when we introduced the new methods. Unfortunately, we still need developers to migrate so Node.js 10.0.0 introduces a new deprecation warning that is emitted at runtime whenever the old constructors are used by any code not located in the node_modules directory.

What does that mean in practice? It means you should not see Buffer deprecation warnings emitted from your dependencies, but if your code uses the old Buffer constructors, you will see the warning and will be reminded to update to the new methods.

Error improvements

Of the nearly 300 semver-major commits that have landed in Node.js 10.0.0, most are improvements to error messages and error handling in general. The effort started in Node.js 8.0.0 to assign static error codes to all Node.js produced Error objects has continued and makes a significant leap forward in Node.js 10.0.0. Error messages should be more consistent, more predictable, and generally more useful.

Historically, changes to Error messages and detail have been forced to be treated as semver-major changes within Node.js because the lack of consistency and detail in the Errors themselves has forced user code to parse through error messages to understand the specific kinds of failures that may have occurred. Changing even the punctuation in an Error message could break application code in very awful ways. With the assignment of static error codes, we begin to be able to relax this requirement.

Expect the assignment of Error codes and the improvement of Error messages to continue through Node.js 11.x

File System

The Node.js fs (file system) module has received the most significant internal overhaul it has had likely since it was first introduced. The improvements include restructuring of code for easier maintainability, improved error handling and type checking, and the introduction of a new experimental fs/promises API, featuring Node.js' first first-class Promise-based API.

Use of the new fs/promises API should be straightforward and familiar to anyone already familiar with both the existing fs API and Promises in general:

Plain Text
const fs = require("fs/promises");
async function openAndStat() {
  const fd = await fs.open("somefile.txt", "r");
  try {
    return await fs.fstat(fd);
  } finally {
    await fd.close();
  }
}
openAndStat()
  .then(console.log)
  .catch(console.error);

The new API is still experimental and will emit a warning at runtime when first used. Once we are sure it is stable and there are no significant bugs still lurking, we will remove the experimental status.

HTTP and HTTP/2

HTTP and HTTP/2 have each received a number of incremental improvements in 10.0.0.

For HTTP changes include stricter standards support, improved upgrade header handling, improved Streams API compatibility, and improved error handling.

For HTTP/2, work continues on improving both the internal implementation and public API, with a significant change made to how trailing headers in HTTP/2 requests and responses are implemented.

Plain Text
const http2 = require("http2");
const server = http2.createServer();
server.on("stream", stream => {
  stream.respond({ ":status": 200 }, { waitForTrailers: true });
  stream.on("wantTrailers", () => {
    stream.sendTrailers({ abc: 123 });
  });
  stream.end("Hello World");
});

Overall, work on HTTP/2 is beginning to wind down as the implementation becomes more and more stable and performant. The goal is to move it out of experimental status as soon as possible before Node.js 10.x enters the Long Term Support cycle in October of 2018.

Streams API

A number of important improvements have been made to the implementation of Streams in Node.js 10.0.0 but the standout features are experimental support for async iterators and the introduction of the new pipeline() method.

Async iterators are a new JavaScript language feature that allows asynchronous iteration using a for await () loop. The experimental support that has been added to all stream.Readable class instances make it possible to consume a stream of data using the new syntax:

Plain Text
const fs = require("fs");
async function readFile() {
  let result = "";
  const r = fs.createReadStream("myfile.txt");
  for await (const chunk of r) result += chunk.toString();
  return result;
}

The new pipeline() utility establishes a pipe between multiple streams with proper end-to-end error handling and flow control. It is essentially the same functionality as the userland pump module, implemented and contributed to Node.js core by pump author @mafintosh (Mathias Buus).

Plain Text
const { pipeline } = require("stream");
const fs = require("fs");
const zlib = require("zlib");</p><p>// Use the pipeline API to easily pipe a series of streams
// together and get notified when the pipeline is fully done.</p><p>// A pipeline to gzip a potentially huge tar file efficiently:</p><p>pipeline(
  fs.createReadStream("archive.tar"),
  zlib.createGzip(),
  fs.createWriteStream("archive.tar.gz"),
  err => {
    if (err) {
      console.error("Pipeline failed", err);
    } else {
      console.log("Pipeline succeeded");
    }
  }
);

Related to the efforts to introduce pipeline() are a several of significant behavioral changes to Streams in Node.js including:

  • Always emitting the 'error' event before the 'close' event;
  • Always deferring the 'readable' event using process.nextTick().

Such changes are subtle but critical to ensuring proper operation of the Streams API.

Performance and Diagnostic Monitoring

The experimental trace events mechanism allows the collection of diagnostic information output to a file usable by the Chrome browsers DevTools utility. Previously, this mechanism could only be enabled using a command-line flag when the Node.js process was launched. New to 10.0.0 is a JavaScript API for enabling and disabling trace events dynamically:

Plain Text
const trace_events = require("trace_events");
const tracing = trace_events.createTracing({
  categories: ["node.async_hooks", "v8"]
});
tracing.enable();
// do stuff
tracing.disable();

Also important is the addition of the node.perf.usertiming tracing category which adds the ability to capture Performance API user timing marks and measures in the trace events timeline.

Plain Text
const trace_events = require("trace_events");
const { performance } = require("perf_hooks");
const tracing = trace_events.createTracing({
  categories: ["node.perf.usertiming"]
});
tracing.enable();</p><p>performance.mark("A");
someAsyncFunction(() => {
  performance.mark("B");
  performance.measure("A to B", "A", "B");
});</p><p>tracing.disable();

Lastly, when the node.bootstrap tracing category is enabled using the --trace-event-categories command-line flag, Node.js will automatically record trace events to the timeline marking key moments in the startup of the Node.js binary.

Work is expected to continue within Node.js 10.x and beyond to further extend the tracing information published via the trace events mechanism.

Say Helllo to V8 6.6

Last, but certainly not least, V8 has been updated to version 6.6 in Node.js 10.0.0 with guaranteed forward ABI compatibility with V8 6.7.

V8 6.6 delivers a range performance improvements and updated JavaScript language features that are covered quite well by Google's own V8 release announcement so I won't cover those in detail here. Early testing has shown significant performance improvements across the board with V8 6.6 and we expect to see the trend continue with 6.7 and beyond.

We've run the synthetic hello world benchmark across Node.js 8.11.1, 9.11.1, and Node.js 10.0.0, and have seen a significant improvement in performance over Node.js 8. Moreover, thanks to the tireless work of Benedikt Meurer and the whole V8 team, Promise and async/await execution has improved significantly, resulting in Hapi v17 realizing a 30% increase in throughput. Remember to migrate to Node.js 10 once it reaches LTS status in October.

Next in line for Long Term Support

In October 2018, roughly six months from now, the Node.js 10.x release line will become the next active Long Term Support branch. With the codename Dubnium, Node.js 10.x promises to deliver improved reliability, performance, monitoring, and developer experience.

Source: Node.js Foundation Release Working Group It would be negligent of me not to include a reminder that Node.js 4.x is reaching the end of it's life on April 30th, 2018. If you are still using 4.x (or any earlier version of Node.js), it is well past the time to migrate to a newer version. You can go to Node.js 6.x if necessary, but the ideal Long Term Support target right now is the latest version of Node.js 8.x, which will remain under Active Long Term Support coverage until December 2018 (note: the graphic above is slightly off on the Active/Maintenance transition for 8.x because of a limitation in the tool used to visualize the schedule). You will want to begin working on your transition to 10.x once it enters the Active Long Term Support cycle in October.

Team Effort

While the Node.js project is supported by a broad and diverse ecosystem of individual developers and companies, I want to directly call out the contributions to Node.js 10.0.0 made specifically by my fellow NearFormers. Combined, NearForm-authored contributions account for around 27-28% of the commits included in 10.0.0. Visit the NearForm website to learn more about our commitment to supporting Open Source .

Insight, imagination and expertly engineered solutions to accelerate and sustain progress.

Contact