背景
VSCodeマクロでフォルダの再帰的なファイル一覧を取得する機能を作る際、ネットワークドライブや巨大フォルダを対象にすると処理が長時間ブロックされるリスクがあります。実装した3つの安全装置を紹介します。
1. タイムアウトはPromise.raceで実現
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => reject(new Error('タイムアウト(60秒)')), 60000);
});
const listPromise = listFiles(folderPath, options, mode, (count) => {
progress.report({ message: `${count}件取得済み...`, increment: 1 });
return isCancelled;
});
const result = await Promise.race([listPromise, timeoutPromise]);
Promise.raceに「本処理のPromise」と「一定時間後にrejectするPromise」を渡すことで、本処理が完了する前にタイムアウトが先に発火した場合はそちらが勝つ、というシンプルなタイムアウト機構になります。Node.js標準のAPIだけで実装でき、追加ライブラリは不要です。
2. 最大件数チェックで際限のない走査を打ち切る
const MAX_FILES = 50000;
async function listFilesRecursive(folderPath: string, files: FileInfo[], ...) {
if (files.length >= MAX_FILES) return;
const entries = fs.readdirSync(folderPath, { withFileTypes: true });
for (const entry of entries) {
// ...
if (files.length >= MAX_FILES) return;
}
}
再帰関数の入口と、ループ内の各イテレーションの両方で件数チェックを入れています。件数チェックを入口だけにすると、1回のディレクトリ読み込みで大量のファイルを一気に処理してしまい、上限を大きく超過する可能性があるため、ループ内での早期リターンも必要です。
3. ネットワークドライブを検出して警告
function isNetworkPath(p: string): boolean {
return p.startsWith('\\\\') || p.startsWith('//');
}
if (isNetworkPath(folderPath)) {
warningMessage += '\n⚠️ ネットワークドライブが検出されました。\n' +
'処理に非常に時間がかかる可能性があります。\n';
}
UNCパス(\\server\share)で始まるパスをネットワークドライブと判定し、再帰検索の確認ダイアログに専用の警告文を追加します。ローカルドライブと比べてI/Oレイテンシが桁違いに大きいため、事前に利用者へ注意喚起するだけでも体験が変わります。
4. withProgressでキャンセル可能なプログレス表示
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: "フォルダ一覧を取得中...",
cancellable: true
}, async (progress, token) => {
token.onCancellationRequested(() => { isCancelled = true; });
// ...
});
VSCode標準のwithProgress APIにcancellable: trueを渡すだけで、通知にキャンセルボタンが追加されます。token.onCancellationRequestedのコールバックでフラグを立て、再帰処理側のonProgressコールバックでそのフラグを都度チェックする構成にすることで、途中キャンセルを反映できます。
まとめ
タイムアウト・最大件数・ネットワークパス警告という3つの安全装置は、いずれもNode.js標準APIとVSCode標準APIの組み合わせだけで実装できました。「再帰的にファイルを走査する」機能を作る際は、正常系だけでなくこうした暴走防止策とセットで設計するのが実務では重要です。