別にしんどくないブログ

技術のことや読書メモを書いています

Node.js v16 の主な変更点

f:id:Shisama:20200422011813p:plain

2021/04/20にリリースされたNode.js v16の主な変更点を紹介します。

nodejs.org

M1 MacでもNode.jsが使えるようになります

Node.js v16.0.0は、Apple Silicon、いわゆるM1チップと呼ばれるAppleの新しいチップに対応したNode.jsの実行ファイルが公式で配布される最初のバージョンになります。

v15.xでもソースコードからのビルドでM1 MacでもNode.jsは使えていました。nvmなどインストールして使えていたのもソースコードからビルドしていたからです。

インストーラーの.pkgファイルにはIntel用(darwin-x64)とARM用(darwin-arm64)が両方サポートされています。 なので、以下の公式ダウンロードページからmacOS Installerをクリックしてnode-v16.0.0.pkgをクリックするだけでインストールできます。

.tar.gz形式は別々で配布されています。以下のページからそれぞれダウンロードできます。

https://nod.js .org/dist/v16.0.0/nod.js .org

V8 v9.0

github.com

JavaScriptエンジンV8が新しくなりました。

Node.js v15はV8 8.6でしたので、V8 8.7〜9.0までに追加された機能はNode.js v16以降で使うことができます。

詳細なV8の変更点については公式ブログをご確認ください。

この記事ではJavaScriptに関する内容を紹介します。

v8.dev

Atomics.waitAsync

tc39.es

Atomics.waitAsyncはV8 8.7で追加されました。

まず、Atomics.waitというAPIの説明をします。

developer.mozilla.org

このAPIInt32ArrayまたはBigInt64Arrayの指定された位置に指定された値が格納されるまでスレッドがスリープする機能です。Int32ArrayBigInt64ArraySharedArrayBufferを使ってスレッド間で共有されているときのみ使えます。

const sab = new SharedArrayBuffer(1024);
const int32 = new Int32Array(sab);

Atomics.wait(int32, 0, 0);

// この間に別スレッドなどで変数int32の0番目の値が書き換えられるまで待つ

// 別スレッドで書き換えられた値を表示する
console.log(int32[0]); // 100
// 別スレッドでint32の0番目に100を格納する
Atomics.store(int32, 0, 100);
// 待っているスレッドに書き込みが完了したことを伝える
Atomics.notify(int32, 0, 1);

このように別スレッドの処理が終わるまで処理を待つ事ができます。 しかし、Atomics.waitは同期処理です。

そのためメインスレッドで使ってはいけません。もしメインスレッドでスリープが実行されると、スリープしている間他の処理も止めることになります。

そこでAtomics.waitを非同期で実行できるようにしたのがAtomics.waitAsyncです。`Atomics.waitAsyncはメインスレッドでも使えます。Promiseを返す関数になっています。

const sab = new SharedArrayBuffer(16);
const int32 = new Int32Array(sab);
const atom = Atomics.waitAsync(
  int32, // 対象のtypedArray
  0,     // typedArrayのindex
  0,     // 検証する値
  1000   // タイムアウトする時間
);

atom.value.then(
  (value) => {
    if (value == 'ok') {
      // タイムアウトしなかった場合
      console.log(int32[0]); // 100
    }
    else {
      // タイムアウトした場合
      console.log('Timeout!')
    }
  });

Atomics.store(int32, 0, 100)
Atomics.notify(int32, 0, 1);

Atomics.waitAtomics.notifyAtomics.waitAsyncについては次のV8の記事が詳しいです。

v8.dev

RegExp match indices

tc39.es

正規表現でキャプチャグループの配列のインデックスを取得できます。V8 9.0で追加された機能です。

次のように/dフラグをつけることで.indicesというプロパティが追加されます。.indicesにはキャプチャグループの配列のそれぞれの始点と終点のindexが格納されています。

// 'foo'と'bar'をキャプチャします
const regex = /(foo)(bar)/d;
// 文字列がマッチする
const match = regex.exec('foobar');
// [ 'foobar', 'foo', 'bar' ]

console.log(match.indices);
// [ [ 0, 6 ], [ 0, 3 ], [ 3, 6 ], groups: undefined ]

console.log(match.indices[0]); // [ 0, 6 ] 'foobar'が一致している
console.log(match.indices[1]); // [ 0, 3 ] 'foo'が一致している
console.log(match.indices[2]); // [ 3, 6 ] 'bar'が一致している

RegExp match indicesについてもV8のブログが詳しいです。

v8.dev

Timers Promises APIが安定版になりました

github.com

すでにNode.js v15でも追加されていたtimers/promisesモジュールが安定版になりました。

これは非同期でsetTimeoutのようなタイマー処理をするためのAPIです。

const { setTimeout: sleep } = require("timers/promises");
const main = async () => {
  console.log("start");
  await sleep(10000); // 10秒待つ
  console.log("waited 10 seconds");
};

timers/promisesには3つの関数があります。

  • setTimeout: 指定したミリ秒が経てばPromiseがresolveになる
  • setImmediate: 現在のイベントループが終わったら、すぐ実行
  • setInterval: 指定したミリ秒で繰り返し実行する

それぞれ使ったコードは次のとおりです。それぞれのタイマー処理がresolveされれば、それぞれのメッセージを表示しています。

import { setTimeout, setImmediate, setInterval } from 'timers/promises';

setTimeout(3000, '3秒後に実行').then(console.log);

setImmediate('すぐ実行').then(console.log);

for await (const time of setInterval(1000, Date.now())) {
  const now = Date.now();
  console.log('経過時間:', now - time);
}
$ node timers.mjs
すぐ実行
経過時間: 1006
経過時間: 2016
3秒後に実行
経過時間: 3018
経過時間: 4027
経過時間: 5027

この数バージョンで非同期関数のPromise版がどんどん増えています。fs/promisesに関しては、実際使っている方も多いのではないでしょうか。今回紹介したtimers/promisesも使う頻度が高そうだなと感じています。

fs.rmdirのrecursiveオプションがDeprecatedになりました

github.com

fs.rmdir関数、fs.promises.rmdirfs.rmdirSyncrecursiveオプションがDeprecatedになりました。recursiveオプションは将来的に無視されるようになります。

元々、ドキュメント上ではDeprecatedになっていましたが、v16.0.0からは実行時にDeprecatedの警告メッセージが表示されるようになります。

recursiveオプションを付与することで、指定されたパスのサブディレクトリを再帰的に削除します。rmコマンドの-rオプションと同じ機能です。

たとえば、次のようなrmdirSync関数にrecursiveオプションを使ったコードを実行してみます。

import fs from 'fs';

fs.rmdirSync('./path', { recursive: true });

実行しますが、次のような警告メッセージが表示されます。

(node:45355) [DEP0147] DeprecationWarning: In future versions of Node.js, fs.rmdir(path, { recursive: true }) will be removed. Use fs.rm(path, { recursive: true }) instead
(Use `node --trace-deprecation ...` to show where the warning was created)

代わりにrm関数でrecursiveオプション使って再帰的にディレクトリを削除できます。

また、rm関数にはforceオプションが追加されています。forceオプションはrmコマンドの-fオプションのようにディレクトリを強制的に削除できるオプションです。forceオプションの指定は省略できます。省略したとき、内部的にfalseが設定されます。

recursiveオプションとforceオプションをtrueにすると、rm -rfコマンドと同じことができます。

import fs from 'fs';

fs.rmSync('./path', { recursive: true, force: true });

前述のとおり、将来的にrmdir関数のrecursiveオプションは無視されるようになります。今後はrm関数を使いましょう。

Node.js v15の機能がLTSとして使えるようになる

Node.js v15の紹介ブログでも書いたとおり、Node.js v15には多くの機能が追加されています。

shisama.hatenablog.com

上記の記事で紹介しただけでも次の機能が追加されます。

  • npm v7がNode.jsに同梱される
  • ES2021の新機能
  • Web Crypto API (まだExperimental)
  • AbortController
  • EventTarget
  • QUIC (まだExprimental)
  • stream/promises
  • Unhandle Rejectionsの終了ステータスが1に変更

これらに加えて、マイナーバージョンでBufferatobbtoaが追加されています。

github.com

Node.js v15の機能はもちろんNode.js v16に含まれています。LTSとして多くの機能が使えるようになります。Node.js v16がLTSになるのは2021/10/26を予定しています。

最後に

Node.js v16の変更は少なく感じますが、Node.js v15で追加された機能も含みます。本番環境でLTSのNode.jsを使っている方などには大きな変更になるのではないでしょうか。

特にnpm v7やUnhandled Rejectionsの終了ステータスは大きな変更です。Node.js v16へアップデートする前に検証しておくことをおすすめします。

Node.jsは4月末と10月末にメジャーバージョンのリリースをします。次のv17は10月末ごろにリリースされるはずです。また、今回紹介したNode.js v16は2021/10/26にLTSになります。

最新のリリース情報は以下のリポジトリでご確認ください。

github.com

最後までお読みいただきありがとうございました。不備や質問がございましたら、@shisama_までメンションするかブコメなどでコメントください。

参考記事

nodejs.medium.com

変更履歴

  • (2021/04/22)「fs.rmdirのrecursiveオプションがDeprecatedになりました」の節にrm関数に関する内容を追記しました。