この記事で紹介しているExplicit Resource Managementでは、もはやtry-using文は無くなりました。この記事の内容は最新の情報ではありませんのでご注意ください。
最新のプロポーザルでは、using const文やusing await const文が使われることになっています。(2021年10月)
リソース管理というのはプログラミングにおける頻出課題のひとつです。そもそもリソースとは何かというのは人によって思い浮かべるものが違うかもしれませんが、ここでいうリソースは「使ったらちゃんと後始末(解放)をしないといけないもの」だと思ってください。今時はピンと来ない方もいるかもしれませんが、「ファイルをopen
したらちゃんとclose
する」とかおおよそそういう話です。
このようなリソースは、一度使おうものならその後何が起ころうとも必ず後始末をしないといけません。たとえ使っている途中でエラーが起こったとしても、適切なエラーハンドリングを行なって忘れずにリソースの後始末をする責任があります。リソース管理を誤ると、メモリリーク等の原因になりかねません。
この記事では、このような「リソース管理」を補助してくれるJavaScriptの新しい言語機能を紹介します。今回紹介するECMAScript Explicit Resource Managementは、つい先日Stage 2になったばかりのプロポーザルです。Stageとは何かみたいな話はこの記事では語りませんが、ざっくり言うと方向性は定まってきたという感じです。もちろん、まだ仕様策定途中であり確定したものではなく、当然ながらブラウザやnode.js等には実装されておらず今試すことはできません。記事タイトルの「超モダン」というのは新しすぎて今はまだ利用不可という意味なのであしからずご了承ください。1〜2年もすれば最新鋭の言語機能になっているかもしれませんから、期待して待ちましょう。
なお、知っている方向けに一言でいうとこれはC#のusing
文やPythonのwith
文, そして**Javaのtry-with-resources
**文に相当する言語機能です。機能的にはJavaが一番近いですね。
try-using
文を用いるリソース管理の概要
早速中身に入っていきましょう。今回紹介する機能はだいたいこんな感じの構文です。
try using (const resource = makeResource()) {
// この中ではresourceが使用可能
resource.write(/* ... */);
}
// try文を出ると同時にresourceが解放される
見ると分かるようにtry using (...) { ... }
という形の**try-using
文**が追加されています。後でより詳しい説明がありますが、このように()
の中で変数宣言をすることができ、try-using
文のブロックの中でこの変数を使用可能です。
この変数に入っているものがリソースであり、try
文から出るときに自動的にこのリソースに対して“後始末”が行なわれます。後始末というのは具体的にはリソースが持つ[Symbol.dispose]
メソッドの呼び出しです。
新しい構文の詳細
上では一例を紹介しましたが、このプロポーザルで追加されるtry-using
構文には主に2種類があります。
一つは上で紹介したように()
の中で変数を宣言する構文です。この構文では、const
による変数宣言のみ可能です。すなわち、var
やlet
による変数宣言は構文エラーとなりできません1。
ここで宣言された変数はtry-using
文の{ ... }
の中でのみ使用可能です。そして先ほどの述べたとおり、ブロックから出るときに[Symbol.dispose]
メソッドの呼び出しによってリソースが解放されます。
try using (const resource = makeResource()) {
// この中ではresourceが使用可能
resource.write(/* ... */);
// ブロックの終わりで resource[Symbol.dispose]() が呼び出される
}
もう一種類の構文は、変数宣言を行わずに()
の中に何らかの式を書くものです。
try using (何らかの式) {
// ...
}
この場合も{ ... }
の終了時に何らかの式
の結果得られたオブジェクトに対して[Symbol.dispose]
メソッドが呼び出されます。つまり、この構文は以下と同じということになります。
try using (const _unused = 何らかの式) {
// ...
}
すなわち、変数に入れてもいいけど別に使わないという場合の糖衣構文ということになります。こちらの構文は、主に「try-using
文に入ると同時にリソースを作成するのではなく、既存のリソースをtry-using
文に当てはめたい」という場合に有用でしょう。
エラー発生時の挙動
最初に述べた通り、リソース管理において重要なことは「いかなる場合でも確実にリソースの後始末を行う」ことです。このtry-using
文はそのための機能を持っています。
一言で言うと、「try-using
文のブロックから途中で脱出する場合でも脱出直前にリソースの[Symbol.dispose]
メソッドが呼ばれる」という挙動になります。
try using (const resource = makeResource()) {
// ブロックの中でエラー発生!!!!!!
throw new Error("OMG");
// ↓当然これは実行されない
console.log("Hi");
}
この例の場合、このtry-using
文はcatch
(後述)を持たないので、中で発生したエラーは普通に上に伝播していきます。ただ、エラーがこのtry-using
文から脱出するタイミングでしっかりとresource[Symbol.dispose]()
が呼ばれることになります。
より具体的な例を示すとこんな感じです。今回のリソースは解放されるとconsole.log
するリソースにしましょう。
function makeResource() {
return {
[Symbol.dispose]() {
console.log("disposed");
}
}
}
// エラーをキャッチするための外側のtry文
try {
console.log("1");
try using (const resource = makeResource()) {
console.log("2");
throw new Error("OMG");
console.log("ここは通らない");
}
} catch (err) {
console.log("3");
}
console.log("4");
この例を実行すると1
→2
→3
→4
の順にログが表示されることは言うまでもありません。では、resource
が解放されたことを示すdisposed
はどのタイミングで表示されるでしょうか。
答えは2
と3
の間です。2
の後にエラーが投げられますが、それが内側のtry-using
文を出る瞬間にresource[Symbol.dispose]()
が呼ばれます。その後エラーが外側のtry
文にキャッチされることになります。
また、この例のようにエラーが投げられた場合に限らず、どのような理由でtry-using
文を脱出する場合であっても[Symbol.dispose]
は呼ばれます。ブロックの中からreturn
やcontinue
などで脱出する場合なども例外ではありません。そのような挙動は既存のtry-finally
文でも可能ですが、今回の新構文はそれをより便利に行えるものとなっています。
catch
やfinally
との組み合わせ
この記事で紹介しているtry-using
文は、実は既存のtry
文と同様に**catch
やfinally
と組み合わせることができます**。もちろん、全部ではなくtry using
とcatch
のみ、あるいはtry using
とfinally
のみということも可能です。全部使う場合はこんな感じの構文になります。
try using (const resource = makeResource()) {
// 1. resource を使う処理
} catch (error) {
// 2. エラー処理
} finally {
// 3. 最後の処理
}
お察しの通り、この新しいtry-using
文もやはりエラーをキャッチできる機能を持っています。これまでの例ではcatch
が無いので内部で発生したエラーは普通に上に伝播していきましたが、catch
文がある場合は内部(上の例の1
の処理)で発生したエラーをキャッチすることができます(2
の処理)。
この構文は、従来のエラーキャッチ機能に加えてさらにリソース解放機能も併せ持っています。リソースが解放されるタイミングは**1
の処理の直後**となります。ポイントは、catch
やfinally
に入るよりも前に解放されるということです。
処理の流れを確認してみましょう。1
でエラーが発生しなかった場合は以下の挙動となります。
-
1
の処理を実行する。 -
resource[Symbol.dispose]()
を呼ぶ。 -
3
の処理を実行する。
一方、1
でエラーが発生した場合は以下の挙動となります。
-
1
の処理を実行する(エラーで中断)。 -
resource[Symbol.dispose]()
を呼ぶ。 -
2
の処理を実行する。 -
3
の処理を実行する。
2
や3
の中ではリソースが使えないという点は間違いやすいので注意が必要です。try-using
文でconst
宣言した変数が1
の中でしか使えないと言う事実がこれとうまく噛み合っていますね。
分割代入
try-using
文の初期化でconst
を用いる場合、const
における分割代入もサポートされています。具体的にはこういう感じの用法です。
try using (const {read, write} = makeResource()) {
// ...
}
ただ、一つたいへん注意しなければいけないのは、この場合何に対して[Symbol.dispose]
が呼ばれるのかということです。実は、分割代入によって作られた変数ひとつひとつに対して[Symbol.dispose]
が呼ばれます。
ということは、このブロックから脱出した際にはread[Symbol.dispose]()
とwrite[Symbol.dispose]()
が呼ばれるということです。
さらに言えば、下のように書くのとは意味が違うということです。下のコードではリソースの解放はresource[Symbol.dispose]()
ですから、うっかり下のコードを上のコードにリファクタリングしてしまうと意味が変わります。
try using (const resource = makeResource()) {
const {read, write} = resource;
// ...
}
ちょっと非直感的な気もしますが、この挙動についてはTC39ミーティングでも議論になっています。経緯が気になる方は議事録を探してみましょう。
()
内で複数の変数を宣言する
実は、try-using
文の中のconst
で複数の変数(変数でなく分割代入でも構いませんが)を宣言することもできます。
try using (const a = makeResource(), b = makeResource()) {
// ...
}
これはおおよそ以下と同じ意味です2。
try using (const a = makeResource()) {
try using (const b = makeResource()) {
// ...
}
}
注目すべき点は、この場合上の書き方でも下の書き方でも必ずb
→a
の順にリソースが解放されるということです。try-using
で複数のリソースを同時に宣言する場合は宣言と逆順にリソースが解放されるのです。これは次の話とも関わっています。
リソース初期化中のエラー処理
先ほど、この新しいtry-using
文はエラー処理もできるということを紹介しました。実は、今回エラー処理に指して考えないといけないことが少し増えています。それは、{ ... }
の中だけでなく(...)
の中でエラーが発生することがあるということです。
try using (const resource = makeResource()) { // ←ここでエラーが発生したら?
console.log("1");
} catch (error) {
console.log("2");
}
この例で、makeResource()
でエラーが発生したらどうなるでしょうか。まず、console.log("1");
は実行されません。リソース初期化中にエラーが発生した場合はtry-using
のブロックの中身には入らないのです。
そして、ちゃんとcatch
ブロックは実行されます。すなわち、実行結果としては2
だけ表示されるということになります。
なお、resource[Symbol.dispose]()
は実行されません。というか、リソースが生成される(resource
にリソースが代入される)より前にエラーが発生したのでそもそも解放すべきリソースがありませんね。
では、次の例はどうでしょうか。
try using (const resource1 = makeResource(), resouce2 = makeResource()) {
console.log("1");
} catch (error) {
console.log("2");
}
先ほど紹介した機能を用いて、2つのリソースを一緒に初期化しました。ここで、2回目のmakeResource
でエラーが発生した場合を考えましょう。ポイントは、resource1
は既に初期化が完了している(=解放する責任がある)ということです。
この場合、まずリソース初期化中にエラーが発生したということで、try
のブロックの中身は実行されません("1"
は表示されません)。
そして、catch
ブロックに入る前にresource1
が解放される、すなわちresource1[Symbol.dispose]()
が呼び出されます。あとは先ほどと同じです。
このようなレアケースでもちゃんとリソースが解放できるようになっていてとても偉いですね。
ちなみに、リソース初期化中のエラーにはもうひとつバリエーションがあります。それは、リソースが[Symbol.dispose]
メソッドを持っていなかった場合です。リソースが[Symbol.dispose]
メソッドを持っているかどうかはリソースの初期化時にチェックされ、持っていなかった場合は即座にエラーとなります。
[Symbol.dispose]
メソッド取得のタイミング
これまでいくつかtry-using
文の注意点を説明しましたが、説明すべきことがもう1つあります(まだ実装もされていないのに時期尚早だとか言わないでくださいね)。
それは、[Symbol.dispose]
メソッドが取得されるのは**try
ブロックに入る前**であるということです。すなわち、try
ブロックの中でリソースの[Symbol.dispose]
メソッドを書き換えても反映されません。下の例で考えてみましょう。
function makeResource() {
return {
[Symbol.dispose]() {
console.log("disposed!");
}
};
}
try using (const resource = makeResource()) {
// tryブロックの中でリソースの[Symbol.dispose]メソッドを書き換えている
resource[Symbol.dispose] = () => {
console.log("Hello, world!");
};
}
この例でtry-using
文から出たときには何が表示されるでしょうか。
答えは"disposed!"
です。try-using
にさしかかった瞬間にresource[Symbol.dispose]
が裏で保存されており、それがリソース解放時に呼び出されます。よって、[Symbol.dispose]
メソッドをあとで書き換えても反映されないのです。
リソース解放中のエラー処理とAggregateError
実は、この記事で扱っているプロポーザルは、try-using
文の登場にあわせて新しいエラーオブジェクトをひとつ定義しています。それがAggregateError
です。これは端的に言えば複数のエラーをひとつにまとめたもので、try-using
文の処理が始まってからcatch
に到達するまでに複数のエラーが発生するシチュエーションに対応するために導入されたものです。
そのようなシチュエーションが実際に発生するのはリソースの解放中にエラーが発生した場合、言い方を変えれば[Symbol.dispose]
メソッドがエラーを発生させた場合です。
具体例を見てみましょう。
// 開放時にエラーが発生する変なリソースを作る
function makeResource() {
return {
[Symbol.dispose]() {
throw new Error("dispose error");
}
};
}
try using (const resource = makeResource()) {
throw new Error("error in try");
} catch(error) {
console.log(error); // ←このerrorは何?
}
今回makeResource()
で作られるのは、開放するとエラーが発生するという変なリソースです。上の例を実行するとどうなるか考えてみましょう。
まず、リソースの初期化は問題なく完了してtry
ブロックの中が実行されます。そこにはthrow
文が待ち構えており、error in try
というエラーが投げられます。
エラーが発生したので次はcatch
ブロックに移らないといけませんが、その前にresource
を解放しなければいけません。そのためresource[Symbol.dispose]()
が呼び出されます。そうなると、ここで2つ目のエラーdispose error
が発生することになります。
ということは、try
ブロック(およびリソース)の処理でエラーが2つ発生してしまいました。このときcatch
ブロックが受け取るエラーは2つのうちどちらなのでしょうか。
答えは両方です。より正確には、両方の情報を持ったAggregateError
オブジェクトが作られそれがcatch
ブロックに渡されます。AggregateError
オブジェクトはerrors
プロパティを持ち、これがその中に含まれるエラーの配列となっています。
つまり、上の例のerror
はおおよそ次のようなオブジェクトになっています。
AggregateError {
errors: [Error("error in try"), Error("dispose error")]
}
try
文の場合は配列のエラーは発生した順番に入っています。
いずれにせよ、発生したエラーの情報を漏らすこと無く得られるのはいいデザインですね。時代によっては「最後のエラーオブジェクトのみcatch
ブロックに渡される」みたいな言語仕様になっていたかもしれません。
余談ですが、複数のエラーをまとめたオブジェクトの需要はPromise.anyという別のプロポーザルでも発生しており、AggregateError
はそちらのプロポーザルにも登場しています。
これからのエラーハンドリングはAggregateError
の考慮が求められると言えるでしょう。
try-using-await
文
実はtry-using
文にはasync
関数の中でのみ使える亜種があります。それはtry-using-await
文です。
try-using-await
文は通常のtry-using
文と同様の挙動をしますが、一つ違いがあります。それは、リソースの解放時に呼ばれるメソッドが[Symbol.dispose]
ではなく[Symbol.asyncDispose]
であるということです。
[Symbol.asyncDispose]
はリソースの解放を非同期的に行うことができます。[Symbol.asyncDispose]
が返り値としてPromise
を返した場合、try-using-await
文は自動的にそのPromise
をawait
します。
なお、非同期的な解放のサポートはオプショナルです。つまり、try-using-await
文の場合でも、リソースが[Symbol.asyncDispose]
メソッドを持っていなかった場合は通常の[Symbol.dispose]
が代わりに使用されます。
試しに、解放に1秒かかる変なリソースを作ってみましょう。
function makeResource() {
return {
async [Symbol.asyncDispose]() {
await sleep(1000); // sleep関数は予めいい感じに定義しておく
console.log("disposed!");
}
}
}
async function main() {
try using await (const resource = makeResource()) {
console.log("1");
}
console.log("2");
}
main();
main
関数の中に注目してください。try-using-await
でリソースを使っていますが、このプログラムの挙動はどうなるでしょうか。
結果は、まず1
が表示され、1秒後にdisposed!
と2
が表示されます。この1秒という待ち時間がresource[Symbol.asyncDispose]()
の呼び出しに由来するものです。try-using-await
文により自動的にresource[Symbol.asyncDispose]()
が呼び出されますが、その返り値は上の定義の通り、1秒後に解決されるPromise
です。try-using-await
文はこのPromise
を自動的にawait
するため、try
ブロックから出るときに1秒待つという挙動になります。
先述のように、リソースが[Symbol.asyncDispose]
を持たない場合は[Symbol.dispose]
にフォールバックされます。解放に非同期処理が必要ないリソースはフォールバックさせるとよいでしょう。逆に必ず非同期的に解放しないといけない場合は、[Symbol.dispose]
をそもそも用意しないか、呼ぶとエラーになるようにするのがよいでしょう。TypeScriptサポートのことまで考えると、そのような場合は上の例のように[Symbol.dispose]
をそもそも持たないようにするのがベストプラクティスとなりそうです。
それにしても、try using await
というキーワード3連続は威圧感がありますね。まあ中で暗黙的に何かをawait
する構文は構文中にawait
と明示する方針のようなので仕方ありません。既存のfor-await-of
文も非同期イテレータのnext()
の返り値を暗黙のうちにawait
しています。
イテレータと[Symbol.dispose]
以上でtry-using
及びtry-using-await
文の紹介は終わりです。
このプロポーザルが仕様として導入されれば、既存のオブジェクトやDOM由来のオブジェクトの中にもリソースとしてtry-using
文と一緒に利用可能なものが出てくるでしょう。このプロポーザルではその一例としてイテレータに対して[Symbol.dispose]
メソッドが定義されています。
イテレータの[Symbol.dispose]
メソッドは自身のreturn
メソッドを(存在すれば)呼び出すものとして定義されています。ジェネレータ関数により作られたイテレータはちょうどreturn
メソッドを持っていますから、ジェネレータ関数との併用が期待されていることが分かります。
ジェネレータ関数によって作られたイテレータのreturn
メソッドは、動作中のジェネレータ関数を強制終了させる動作をします3。そうなると何が嬉しいかというと、ジェネレータ関数がtry-using
文で止まっていた場合そのリソースを解放できます。
ちょっと長いですが例を用いて説明します。
function makeResource() {
return {
[Symbol.dispose]() {
console.log(`resource is disposed!`);
}
};
}
// リソースからひとつずつデータを読みだす関数(のつもり)
function* readFromResource() {
try using (const resource = makeResource()) {
while (true) {
yield 1;
}
}
}
// イテレータをリソースとして使う
try using (const iter = readFromResource()) {
// 3回データを読んで終わり
iter.next();
iter.next();
iter.next();
}
// ここで "resource is disposed!"と表示される
この例ではreadFromResource
というジェネレータ関数を定義しました。このジェネレータ関数は内部でリソースを使用しており、try-using
文の内部に留まってyield
でデータを発生させ続けます。
例の最後のところでreadFromResource()
を使っています。readFromResource
はジェネレータ関数なので呼び出すとイテレータが返りますが、このプロポーザルではイテレータが[Symbol.dispose]
を持っているのでこの例のようにリソースとしてtry-using
文で使うことができます。内部では適当にイテレータを使っています。
注目すべきは、イテレータを使い終わった後です。このタイミングで"resource is disposed!"
と表示されます。
これはどういうことかというと、まずtry-using
文を脱したタイミングでiter[Symbol.dispose]()
が呼ばれます。それはiter.return()
を呼び出すため、readFromResource()
のジェネレータ関数が強制終了します。ということは、readFromResource
内のtry-using
文から脱出したことになるためresource[Symbol.dispose]()
の呼び出しが発生します。これが"resource is disposed!"
というログを表示するのです。
結局この例で解決したかった問題は何かというと、ジェネレータ関数により作ったイテレータを放置するといつ使い終わったのか分からないという問題です。
上の例のreadFromResource
ジェネレータ関数は、使い続ける限りはずっとtry-using
文の中にいます。これは、イテレータが使われ続ける限りはずっとリソースを使い続けていることを意味します。イテレータを使い終わったあと(明示的に終了させることなく)この状態で放置されると、このリソースは解放されることがありません。これを防ぐためにイテレータのreturn()
メソッドでジェネレータ関数を明示的に強制終了させなければいけないのです。return()
メソッドが呼び出されるとジェネレータ関数の制御が外に脱出しようとするため、try-using
文の外に出た扱いとなり無事にリソースが解放されます。
そして、イテレータの[Symbol.dispose]
メソッドは自動的にこのreturn()
メソッドを呼び出すものとなっています。別の言い方をすれば、イテレータ自体が裏にジェネレータ関数を持っているリソースであり、イテレータを解放することで裏のジェネレータ関数(さらには裏のジェネレータ関数が保持しているリソース)が解放されるということになります。
今後try-using
文を使うにあたってはこのような「何がリソースであるか」という考え方が重要になってくるでしょう。イテレータとジェネレータ関数というのは極端な例ですが、何らかのリソースを裏で保持しているオブジェクトは一般に自身もリソースとなり、自分が解放されたら自分が持っているリソースを解放しなければいけません。
そんなに難しくない言語仕様のわりには考えさせられることが多いですね。それだけリソース管理は難しいということでしょう。
仕様の変遷
最後に、少しこのプロポーザルの変遷に触れたいと思います。プロポーザルが現在の形になったのはStage 2に上がると同時なので、この先は比較的安定することが予想されます。逆に言えば、Stage 1の間は結構仕様がダイナミックに変化していました。
まず、最初の草案ではtry-using
文ではなく単なるusing
文でした。C#と同じ形ですね。
using (const resource = makeResource()) {
// ...
}
さらに()
の中はconst
宣言だけでなく式も可能ですから、using(resource) { ... }
のような形も可能となります。
この案の問題点は、なんと言ってもusing
がキーワードではない点でしょう。そのため、using
という関数を呼び出しているのと紛らわしいという問題が発生します。これはusing(resource)
まで構文解析しても解決できず、そのあとに{
が来るかどうかまで調べないとどちらなのか分かりません。また、悪名高き自動セミコロン挿入によって、従来のJavaScriptでも次のようなコードが存在し得ます。
using(resource)
{
// ...
}
これはusing(resource)
という関数呼び出しと{ ... }
というブロックの2つを逐次実行するというプログラムです。機能追加で既存のプログラムの意味が変わるのはまずいですから、必然的に新しいusing
構文ではusing(...) { ... }
の)
と{
に改行が入ってはいけないというルールが必要になります。これはたいへん分かりにくいし不便ですね。
というような事情があり、using
のみを用いる案は頓挫しました。
次に出てきたのがtry
だけを用いる案です。この時点で既存のtry
文が持つエラーキャッチ機能が今回のプロポーザルの機能の一部として導入されました。これはJavaのtry
文が持つ機能と同様ですから、一気にC#寄りからJava寄りに変遷したことになります。
try (const resource = makeResource()) {
// ...
} catch(err) {
// ...
}
try
は既にキーワードなので既存のプログラムにtry(...)
という関数呼び出しは存在せず、また既存のtry
文はtry { ...
という形で始まるので新構文ともぶつかりません。構文上の曖昧性は回避できています。
ならばこの方向性で進むかと思いきや、今度は既存のエラーをキャッチできるtry
文がある中でtry
とだけ書いてリソース管理ができるのはさすがに分かりにくいという問題が挙がりました。
ということで、分かりやすさと構文上の問題点を考慮した結果、最終的にJavaとC#の折衷とも言えるtry using
文が誕生したのです。めでたしめでたし。
ちなみに、ここでは構文の変化を取り上げましたが他にも細かい仕様があっちに行ったりこっちに行ったりしていす。具体的にはtry using
文の()
の中で分割代入したとき何がリソースとして扱われるのか(現在の仕様では分割代入で作られた変数ひとつひとつがリソースですが、当初の案では分解される前のオブジェクトがリソースとして扱われていました)、またリソースに[Symbol.dispose]
メソッドが無かった場合エラーはいつ発生するのか(現在の仕様ではtry
ブロック開始時ですが、当初の案ではリソース解放時でした)といった違いがあります。細かい話なので興味がある方は調べてみましょう。
まとめ
この記事では、JavaScriptにリソースという概念を持ち込むExplicit Resource Managementというプロポーザルを紹介しました。筆者はC#はやらないのですがC#のusing
文は結構いいという話を聞きますから、きっとJavaScriptのtry-using
文もいい感じなのだろうと信じています。
このプロポーザルの登場によって、将来的にJavaScriptプログラムにおけるリソース管理というのがどのような形になるのかが見えてきました。いくつかの例を通してご紹介した通り、try-using
文という便利な構文があってもなおリソース管理には考えることが結構あります。ぜひこの記事を通して今のうちにリソースの概念を理解しておきましょう。このプロポーザルがStage3くらいになれば、利用例にtry using (const thing = new Something())
なんて書いてあるライブラリが出てきてもおかしくありません。いざその時になっても怯まないように備えておきたいものですね。
関連記事
ある意味この記事と関連している筆者の既存記事をひとつご紹介します。
この記事ではWeakRef
という別のプロポーザルを紹介しています。こちらが構文上のexplicit(明示的)なリソース管理ならあちらはGCによるimplicit(暗黙的)なリソース管理と言えるかもしれません。
-
ただし、これらのキーワードを使わずに既存の変数に
=
を用いて再代入する場合は、この代入文が式と見なせるため、後述のもうひとつの構文(()
の中に式を書くもの)に当てはまるため可能となります。すごく紛らわしいので注意しましょう。 ↩ -
おおよそというのは、スコープの作られ方の違いやエラー処理のされ方(後述)の違いに表れます。 ↩
-
正確には、停止中の
yield
式からreturn
completionを発生させます(completionについては筆者の既存記事JavaScriptの{}を理解するで詳しく解説しています)。 ↩