LoginSignup
11
6

More than 3 years have passed since last update.

JavaScriptで同期的にsleepする方法 (通常用途には使わないでください)

Last updated at Posted at 2018-11-11

JavaScriptでsleepするには通常、setTimeout() を使用します。

setTimeout(() => {
    // do something after 100ms.
}, 100);

setTimeout()に渡したコールバック関数は、JavaScriptランタイムのメッセージキューに登録されます。
setTimeout()の呼び出し元は、コールスタックのすべての関数がreturnすることで、ランタイムに処理が戻ります。
ランタイムは、メッセージキューにすぐに実行できる関数があれば実行し、なければ待ちます。

メッセージキューには setTimeout()で登録されたもののように、一定時間後にならなければ実行できないものと、setImmediate()Promiseで登録されたもののように即座に実行できるものがあります。

メッセージキューが空になると、Node.jsの環境であればプログラムが終了します。

setTimeout()のスリープで何が問題か?

setTimeout()は、上述の通り現在のコールスタックがすべてreturnしなければ処理が始まらないのですから、現在のコールスタックを維持しなければならない場合、sleep後に何かが呼ばれても意味がありません。

私の場合は、インタープリタを作っていて、ブレークポイントでデバッガの関数を起動し、標準入力(TTY)から入力を待つという状況で問題となりました (そのコールスタックに積まれたものをデバッグしたい)。

でも、Promise化して await で待てるんじゃないの?

確かに、すべての呼び出し元が async となるように設計すれば可能ですが、すべての呼び出しがメッセージキュー経由となるオーバーヘッドは許容できませんし、また、同インタープリタではユーザーの任意のJavaScript関数もインタープリタの関数として組み込める機構となっているため、利便性からもNGでした。

同期的に待つ方法

Atomics.wait() - JavaScript | MDN
SharedMemory と Atomic API について

比較的新しい機能である worker の、worker間で共有メモリを利用して通信するための SharedArrayBuffer Atomics を使用することで、同期的にsleepすることができます。

const sab = new SharedArrayBuffer(8);
const s32ar = new Int32Array(sab);
Atomics.wait(s32ar, 0, 0, 100); // 100ms待つ

注意(2020/3/9追記)
SharedArrayBufferはSpectre脆弱性対策として一部ブラウザでは引き続き無効化されています
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer

副作用 (大変危険)

イベントループが回らないので、非同期I/Oがすべて止まります。
Promiseに依存した処理も止まります。
NodeのAPIも、どれが動いてどれが動かないか確実なことは言えません。
ただ、システムコールの薄いラッパーになっているAPIは動作すると考えられます。
Node 10,11 の環境で、 fs.readSync fs.writeSync process.stdout.cursorTo process.stdout.clearScreenDown が動作することは確認できました。

結論

軽い気持ちで使うと、開発チームメンバーからしばかれること必至です。
普通のsleep目的で使ってはいけません。

11
6
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
11
6