0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScriptのusingは、returnや例外の後で何を解放するか。9条件で確認する

0
Last updated at Posted at 2026-11-05

usingは、解放用のメソッドを持つオブジェクトをスコープへ登録し、そこを抜けるときに後始末を呼ぶ構文です。複数のリソースを用意した場合は、取得した順と逆に解放されました。途中のreturnでも同じです。

Node.jsで9条件を試し、解放順、例外の残り方、非同期の待機まで確認しました。使う前に、どのオブジェクトがdisposeメソッドを持ち、どのスコープを抜けると解放されるかを確認します。

returnより先に解放が終わる

次のコードを.mjsとしてNode.js 24.15.0で実行しました。変換ツールやpolyfillは使っていません。

const events = [];
function open(name) {
  events.push(`open:${name}`);
  return { [Symbol.dispose]() { events.push(`close:${name}`); } };
}
function work() {
  using first = open('A');
  using second = open('B');
  events.push('body');
  return 'done';
}
const value = work();
console.log(JSON.stringify({ value, events }));

結果はvalue: "done"、eventsはopen:A → open:B → body → close:B → close:Aでした。呼び出し元が戻り値を受け取った時点で、両方の解放が終わっています。

openは実験用のオブジェクトを返すだけです。Symbol.disposeメソッドの呼び出しを記録しており、ファイルや接続を開いたふりをした実測値ではありません。ECMAScriptの宣言とスコープの仕様でも、usingで登録したリソースをスコープの処理終了時に解放する流れを確認できます。

通常終了に加え、途中の失敗も比較しました。

条件 確認できた順序・結果
A、Bを取得して通常終了 B、Aの順に解放
Aを取得してreturn Aを解放してから呼び出し元へ戻る
本体でthrow Aを解放してからcatchへ進む
A取得後、Bの取得がthrow 登録済みのAを解放。Bの解放は呼ばれない
本体とA・Bの解放がすべてthrow 両方の解放を試し、例外をSuppressedErrorへ保持
await usingでA、Bを登録 Bの非同期解放の完了後にAを解放
asyncDisposeだけの値をusingへ渡す TypeError。非同期解放は呼ばれない
nullとundefinedをusingへ渡す 解放メソッドなしで本体を実行
一時ディレクトリ内でthrow catchへ進んだ後にはディレクトリが存在しない

Bの取得そのものが失敗した場合、usingがBを後から解放するわけではありません。取得関数が内部で途中まで確保したものは、その関数側で後始末できる設計が必要です。

解放でも失敗したら、本体の例外はどこへ行くか

比較用のdisposeメソッドからもthrowさせました。本体がbody failed、Bがclose failed:B、Aがclose failed:Aを投げる条件です。

最後に捕まえた例外のerror.messageはclose failed:Aでした。ただし、本体の失敗が消えたわけではありません。

参照先 記録されたメッセージ
error.error.message close failed:A
error.suppressed.error.message close failed:B
error.suppressed.suppressed.message body failed

外側と内側のラッパーはSuppressedErrorです。解放中の失敗があっても残りの解放を試み、先に起きた例外を関連付けて保持していました。明示的リソース管理の設計資料も、この例外の関係を説明しています。

ログを一番外側のmessageだけで済ませると、処理本体の失敗を見落とす可能性があります。今回の記録ではerrorとsuppressedをたどって、それぞれのメッセージを確認しました。

非同期の解放にはawait usingを使う

非同期版はSymbol.asyncDisposeの開始と完了を別々に記録しました。await using a = ...、await using b = ...と登録すると、順序はbody → close start:B → close end:B → close start:A → close end:A → after workでした。

この実験の待機はPromise.resolve()です。通信やディスクI/Oの所要時間を測ったものではありません。それでも、Bの解放完了を待ってからAを解放し、両方が終わってから呼び出し元が進む順序は確認できます。

Symbol.asyncDisposeしか持たないオブジェクトを、awaitのないusingへ渡す比較ではTypeErrorになりました。非同期の処理を返す値なら、単にusingへ置けばよい、という使い方にはできません。

Node.jsの一時ディレクトリでも確認する

ログ用オブジェクトだけでなく、実際に作った一時ディレクトリを使いました。

import { existsSync, mkdtempDisposableSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

let path;
try {
  using temporary = mkdtempDisposableSync(join(tmpdir(), 'article-using-example-'));
  path = temporary.path;
  writeFileSync(join(path, 'sample.txt'), 'fixture');
  console.log('inside:', existsSync(path));
  throw new Error('work failed');
} catch (error) {
  console.log('caught:', error.message);
}
console.log('after:', existsSync(path));

出力はinside: true、caught: work failed、after: falseでした。sample.txtを含むディレクトリが、例外でスコープを抜けたときに削除されています。

Node.js 24.15.0のmkdtempDisposableSyncが返すオブジェクトには、pathと削除用のdisposeメソッドがあります。このAPIは24.4.0で追加されたものです。任意のファイルパス文字列へusingを付けても、同じ削除処理が用意されるわけではありません。

検証日は2026年9月11日、Node.js 24.15.0、macOS arm64です。9条件のコードと結果を保存しました。別のNode版、ブラウザー、変換後コード、プロセスの強制終了は検証していません。今回確認したのは、JavaScriptがスコープ退出の処理を実行できる状況での解放です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?