おっと、これはasync/awaitの表現の限界ですね。
async関数を宣言するとthrow等の挙動がPromiseのrejectを叩くような挙動に変化しますが、
別のコールバック関数の中身に入ると、
そういったasync関数特有の挙動の効果が消えます。
なので普通にJS上でエラーを投げただけになります。
そしてsetTimeout(fn, 5000)
の中でthrow
しているのが問題で、
JavaScriptが持っているイベント置き場に「実行して関数」と「達成条件」のセットを設置して(イベント登録)
動作終了後にイベントを達成しているかな?と巡回して関数を実行してくれるイベントループというフェイズにて実行してもらう挙動をします。
なのでasync関数以外の非同期関数内でthrowを投げるとcatch出来ずに即座にエラー落ちします。
try-catchつかえねぇ……
というわけで何かしらの別手段をひねり出す必要があります。
じゃあどうすればええねん
考えられる大まかなプランとしては2つあります。
- child_processの中でも後からコマンド実行状況に割り込めるspawnを使う
- Promiseインスタンスをasync関数による暗黙の生成に頼るのではなく、自分で生成して引数であるreject関数を確保する
今回はタイムアウトを実現したい。
つまり一定時間を経過したらコマンド実行に割り込んで動作を停止させる必要があるので
前者のspawnを使うのが正解です。
なのでPromiseを捨てて従来の非同期処理のイベントエミッターを使う事になります。
spawn関数の実行の仕方は公式ドキュメントを見れば理解出来ますね。
その返り値の扱い方から見ていきましょう。
ドキュメントのReturnsの項目を見ると、ChildProcessというインスタンスを返すようです。
これはイベントエミッターを継承して作られたインスタンスなので、onやらemit等の機能が利用できます。
Returns: <ChildProcess>
Extends: <EventEmitter>
ですが、その中でも着目すべきはsubprocess.exitCodeとsubprocess.kill()でしょう。
The subprocess.exitCode
property indicates the exit code of the child process. If the child process is still running, the field will be null
.
和訳: subprocess.exitCode
プロパティは、子プロセスの終了コードを示します。子プロセスがまだ実行中の場合、フィールドはnull
になります。
ここまで見えてれば後は楽ですね。
質問文のコードを利用するとこんな感じになります。
js
1const {spawn} = require("child_process");
2
3client.on("messageCreate", message => {
4 if (message.author.id === "1234") {
5 if (message.content.startsWith("js\n")) {
6 let body =message.content.substring(3);
7 let log = "";
8
9 fs.writeFileSync("sample.js", body)
10 const ls = spawn("node sample.js");
11 ls.on("data", m => log = log + m); // 文字列は小出しに来るので都度log引数に吸い出す
12 ls.on("close", code => {
13 if (code == 0) {
14 log = log || "出力なし";
15 message.reply("```\n" + log + "```\n実行時間:" + (Date.now() - before) / 1000 + "秒"); // console.logの表示内容はJavaScriptのコードというわけではないのでjsは削った
16 return;
17 }
18 message.reply("実行時間が5秒を超えたため強制終了しました");
19 });
20 setTimeout(() => {
21 // exitCodeがnullならば、まだ実行中と見なしてプロセスを終了させる
22 if (ls.exitCode == null) ls.kill("SIGINT");
23 }, 5000);
24 }
25 }
26}
動作検証はしてないので少し不安ですが、
考え方自体はこれで行けると思うので
検証込で実行してみてください。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。