🦔
実践: 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