こんにちは@watildeです。Node.jsでは、主にWHATWG/URLあたりの実装を担当しています。
今回は、2020年10月20日に新しいメジャーアップデートとしてリリースされたv15の変更点のうち、筆者が気になった代表的なものについて紹介します。全てはカバーできていないので、下記にて他の方の解説記事が出たら随時リンクを追加してブログ記事をrequire()
していきます。
各変更の紹介では、なるべく参考リンクを付けているので詳細が気になった際はリンクから一次情報に飛んで読んでみてください。また、情報に誤りがあった場合はTwitterにて教えていただけると助かります。
背景
Node.jsは半年に一度メジャーアップデートを実施しており、4月は偶数のバージョン、10月は奇数のバージョンをリリースすることになっています。新しい奇数のバージョンのリリースが行われると、追いかけるように一つ前にリリースされた偶数のバージョンLTSに移行します。
参考: https://github.com/nodejs/Release#release-plan
今回のケースでは、2020年10月27日にv14がLTSを開始し、2021年10月19日まではfeatureなどもマージされるアクティブLTSに、以降から2023年04月30日まではクリティカルなバグの修正のみを行うメンテナンスLTSへと移行します。
参考: https://github.com/nodejs/Release#release-phases
リリース内容の詳細は、下記のリリースノートと公式ブログのアナウンスにて紹介されています。
参考:
- https://github.com/nodejs/node/blob/v15.x/doc/changelogs/CHANGELOG_V15.md
- https://medium.com/@nodejs/node-js-v15-0-0-is-here-deb00750f278
代表的な変更の紹介
deps: npm v7の導入
npmのv7が同梱されました。新機能としてWorkspaceやacceptDependenciesなどが使えるようになり、Monorepoでの開発が捗るようになります。詳細は下記の参考リンクにて。
process: unhandledRejectionがエラーをthrowするようになる
この変更については、@kimamulaさんが書いた記事が詳細に解説しているので、下記にリンクを貼っておきます。
参考: https://zenn.dev/kimamula/articles/b32d11d52c2b7a733119
ここではコード例を元に簡単に紹介します。
v14以前
Node.js側でハンドリングがなされていないエラーを、UnhandledPromiseRejectionWarningやunhandledRejectionイベントで処理していました。
$ node -p "Promise.reject()" Promise { <rejected> undefined } (node:29606) UnhandledPromiseRejectionWarning: undefined (node:29606) 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(). (rejection id: 1) (node:29606) [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.
しかし、これはそもそも想定外のエラーが発生したということなので、プロセスをエラーで終了するべきだと議論が長きに渡ってなされていました。
参考: https://github.com/nodejs/node/issues/830
v15以降
そして今回のv15では、プロセスをエラーとして終了する変更が行われました。
$ node -p "Promise.reject()" Promise { <rejected> undefined } node:internal/process/promises:218 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 "undefined".] { code: 'ERR_UNHANDLED_REJECTION'
これでデフォルトの挙動は変更されましたが、以前の挙動に戻したい場合は--unhandled-rejections=warn
オプションを渡すことで可能です。
参考: https://github.com/nodejs/node/pull/33021
quic: QUICの実験的サポート
QUICとは、UDP上で動作するトランスポートプロトコルで、TLS 1.3を介した組み込みのセキュリティ向上、フロー制御、接続移行、多重化などが含まれています。詳しくは@flano_yukiさんの書いたHTTP/3の解説を参照。
参考: https://asnokaze.hatenablog.com/entry/2020/06/01/005625
QUICは、netモジュール内で実装されており。--experimental-quic
オプションを渡すことで利用することができます。
const { createQuicSocket } = require('net');
詳細な使い方に関しては、@nwtgckさんが記事にて良いログを残されているので紹介させていただきます。
追記(2020/10/27)
@lekoさんが詳細な記事を書いてたので、追加でリンクをこちらに貼ります。
参考: https://blog.leko.jp/post/http-over-quic-on-nodejs15/
参考: https://github.com/nodejs/node/pull/32379
deps: V8エンジンのバージョンがv8.6に更新
下記の4つの構文が利用可能となりました。それぞれの詳細については参考リンクを貼っておきます。
Promise.any()
- エラー集約:
AggregateError
- 文字列の一括置換:
String.prototype.replaceAll()
- 論理代入演算子:
&&=
,||=
, ??=
ここでは、コード例を元に利用頻度の高そうなPromise.anyについて簡単に紹介します。
Promise.any
は、与えられた複数のプロミスを実行し、1つでも実行が正常に完了されるまで行われるまで実行されます。似た挙動をするPromise.race
では、処理の完了・エラーの発生に関わらず、最初に処理が終わった時点で終了するところが違います。
// Code from https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/any const pErr = new Promise((resolve, reject) => { reject("Always fails"); }); const pSlow = new Promise((resolve, reject) => { setTimeout(resolve, 500, "Done eventually"); }); const pFast = new Promise((resolve, reject) => { setTimeout(resolve, 100, "Done quick"); }); Promise.any([pErr, pSlow, pFast]).then((value) => { console.log(value); }) // 出力内容: "Done quick" Promise.race([pErr, pSlow, pFast]).then((value) => { console.log(value); }) // 出力内容: "Always fails"
参考:
- https://github.com/nodejs/node/pull/35415
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND_assignment
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR_assignment
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_nullish_assignment
timer: timers/promisesの実験的導入
setTimeout
とsetImmediate
のプロミスAPIが実験的に実装されました。WHATWG側では議論中のものが、Node.jsが先行してリリースという形になります。今後、様子見しながらステージを上げていくことになるかと思います。
参考: https://github.com/whatwg/html/issues/617
呼び出しはtimers/promises
内にあり、使い方としては以下の通りになります。
const { setTimeout, setImmediate } = require('timers/promises'); setTimeout(10, null, { ref: false }) .then(console.log); setImmediate(null, { ref: false }) .then(console.log);
参考: https://github.com/nodejs/node/pull/33950
lib: EventTarget関連のインターフェースのグローバル化
下記のモジュールがWeb APIと互換性を持った形でグローバル化されました。開発者・利用者が重なっているからか、ブラウザと挙動が近づきつつありますね。
- Event
- EventTarget
- MessagePort
- MessageChannel
- MessageEvent
参考:
- https://github.com/nodejs/node/pull/35496
- https://developer.mozilla.org/en-US/docs/Web/API/Event
- https://developer.mozilla.org/en-US/docs/Web/API/EventTarget
- https://developer.mozilla.org/en-US/docs/Web/API/MessagePort
- https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel
- https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent
url: URLの仕様追随
こちらもWeb API互換性の話。WHATWG/URLでは日々URLのフォーマットについて仕様が更新されていますが、最近はいくつかの破壊的変更が仕様側で発生したので、v15にて更新を追随されたものがリリースされています。
参考: https://github.com/nodejs/node/pull/35477
dns/promisesの追加
日本のCore collaboratorの@shisama_さんのコミット。紹介記事がどこかで出るのかなと思うので一旦待ち。
参考: https://github.com/nodejs/node/pull/32953
余談
今回、私も実際にメジャーアップデートのPRを出したりレビューの数を一気に増やしてみて分かったんですが、破壊的変更を行うPRは簡単にマージされることがほとんどありません。PRやIssue上で長きに渡る議論が発生し、ときにはWGやTSCで議決を取ることにもなります。例えば、今回の破壊的変更であるunhandledRejection
に関する議論は2015年より行われており(link, link)、破壊的変更についてユーザーへのアンケート(link)とブログでの周知(link)も実施されました。
以上のように、議論ではより良い結論を出すために自分の主張を構造化しつつ、ファクトを交えながら建設的に意見を展開する努力が散見されます。そして重要なのが、全ての意思判断プロセスはオープンであり、誰でも参加できる、ということです。
今回のメジャーアップデートに関して皆さんの中でも何かしら意見が出てくるかとは思いますが、興味が出た方はぜひ https://github.com/nodejs から参加してみてください。