🦔
実践: await using でリソース開放
実践
いつ使うんだこれと思ってたら使う日が来たシリーズ。
今回、Deno で使ったんですが、 Node.js やブラウザでも Polyfill を入れれば動きます。
try finally で puppeteer を終了したい
Deno で puppeteer を扱うために、こういうコードを書いてました。
// original
import puppeteer from "npm:puppeteer@23.6.1";
import chromeFinder from "npm:chrome-finder@1.0.7";
let browser: puppeteer.Browser | null = null;
try {
browser = await puppeteer.launch({
headless: false,
executablePath: chromeFinder(),
});
const page = (await browser.pages())[0];
await page.goto("https://google.com", {
waitUntil: "networkidle0",
});
const title = await page.title();
console.log(title);
} finally {
browser?.close();
}
この browser.close()
を呼び忘れると、イベントリスナーが開放されない判定になり、Deno/Node ではプロセスが終了しなくなります。
逆に、強制的に process.exit(0) や Deno.exit(0) しても、今度は Chrome が終了しなくなります。
なので必ず browser.close()
を呼びたいんですが、 await puppteer.launch(...)
自体が非同期で例外が発生する可能性があり、スコープの外に let で初期化した動的なシンボルを置いています。
これは初期化は一回なので理想的には線形メモリ的に扱いたいんですが、JS にはその機能がありません。
await using 版
というわけでスコープを抜ける時に処理を予約する await using
のための関数を定義して、puppeteer 初期化しつつ browser.close()
を予約します。
import puppeteer from "npm:puppeteer@23.6.1";
import chromeFinder from "npm:chrome-finder@1.0.7";
async function useBrowserContext() {
const browser = await puppeteer.launch({
headless: false,
executablePath: chromeFinder(),
});
const page = (await browser.pages())[0];
return {
browser,
page,
async [Symbol.asyncDispose]() {
await browser.close();
},
};
}
{
await using ctx = await useBrowserContext();
await ctx.page.goto("https://google.com", {
waitUntil: "networkidle0",
});
const title = await ctx.page.title();
console.log(title);
// このスコープを抜ける時に Symbol.asyncDispose() が呼ばれる
}
console.log("Chrome released!");
型を付けるならこういう感じ
async function useBrowserContext(): Promise<AsyncDisposable & {
browser: puppeteer.Browser;
page: puppeteer.Page;
}> {
///...
}
見慣れない書き方なのでちょっと頭の体操感があるんですが、仕組みを理解すれば「リソース確保時に開放処理が予約されている」と認識することができます。
存在は知ってたんですが、はじめて実践的に使えて嬉しくなって記事にしました。こういう新機能は早めに試しておくと、はじめて見た時にビックリせずに済みます。
Discussion
僕も最近知って嬉しくなったので補足情報の共有なのですが、Playwrightの各種クラスに対しても同様に
await using
が使えます!使用例
この記事の try finally 版だと puppeteer.launch に失敗したら代入もされないので let を使う必要がなく
await using 版だと browser.pages にコケた場合の挙動が元コードと異なるように思います
それはそれとして using を使っていると
開放に失敗する可能性がある値を複数使う場合に特に役に立ちます