Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Say Hello to Node.js 10.0.0

DZone's Guide to

Say Hello to Node.js 10.0.0

In this post, we explore the release of Node.js 10.0.0 and how it brings greater stability, performance, and reliability.

· Web Dev Zone ·
Free Resource

Deploy code to production now. Release to users when ready. Learn how to separate code deployment from user-facing feature releases with LaunchDarkly.

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 honor 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 the over 750 commits included in the release.

There’s plenty in the release to get excited about! Here, I only want 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:

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

Results in the rather cryptic and unhelpful error message:

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:

AssertionError [ERR_ASSERTION]: Input A expected to strictly equal input B:
+ expected - actual

  {
-   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:

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

When running Node.js 9.11.1, the output is:

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:

{ 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. Even changing the punctuation in an Error message could break the 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’s 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:

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.

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:

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).

const { pipeline } = require('stream');
const fs = require('fs');
const zlib = require('zlib');

// Use the pipeline API to easily pipe a series of streams
// together and get notified when the pipeline is fully done.

// A pipeline to gzip a potentially huge tar file efficiently:

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 several 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 for 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:

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.

const trace_events = require('trace_events')
const { performance } = require('perf_hooks')
const tracing = trace_events.createTracing({ categories: ['node.perf.usertiming'] })
tracing.enable()

performance.mark('A')
someAsyncFunction(() => {
  performance.mark('B')
  performance.measure('A to B', 'A', 'B')
})

tracing.disable()

Node.js 10.0.0 user timing trace events

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

Node.js 10.0.0 bootstrap trace events timeline

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 Hello 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.

Node.js 10.0.0 Benchmark Results

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.

Node.js Release TimelineSource: 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 its 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.

Deploy code to production now. Release to users when ready. Learn how to separate code deployment from user-facing feature releases with LaunchDarkly.

Topics:
node.js ,node.js v 10 ,web dev ,backend development

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}