Skip to main content

JavaScriptで同期的なwait/sleep/delay関数を作る

ブラウザのDevToolsにコピー&ペーストできるサンプルコード

動作確認は、async/awaitがデフォルトでサポートされているGoogle Chrome推奨です。 1行で書くと次のようになります。

const wait = async (ms) => new Promise(resolve => setTimeout(resolve, ms));

展開型はこうなります。

const wait = async (ms) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(); // setTimeoutの第一引数の関数として簡略化できる
}, ms)
});
}

動作確認

const main = async () => {
console.log(new Date());
await wait(5000); // 5秒待つ
console.log(new Date());
};

await main();

解説

言葉上の表現としては、wait関数が実行されるとPromiseを生成し、指定した秒数後に解決(resolve)されるのを同期的に(呼び出し側でawaitをつける)待つことで遅延関数を実装しています。

補題1. Promiseチェーンで遅延処理を記述する

async/awaitを利用せずにPromiseだけを利用して処理を待機させまう。

console.log(new Date);
const ms = 5000;
new Promise((resolve) => {
setTimeout(() => {
resolve();
}, ms)
}).then(() => {
console.log(new Date);
});

補題2. Promiseもasync/awaitも使わずに遅延処理を記述する

console.log(new Date);
const ms = 5000;
setTimeout(() => {
console.log(new Date);
}, ms);

もう少し解説

async/awaitPromiseを知らない場合、補題2のようなコードを書くことになるでしょう。 これはこれで良いのですが、遅延処理をつなげて書きたい場合に破綻します。 俗称コールバック地獄と呼称します。

コールバックのおさらい

コールバック(callback)のおさらいを簡単にしておくと、引数に関数を渡し、関数の実行タイミングを委ねる処理方法です。

const playGame = () => {
console.log("ゲームする");
}

const goHome = (callback) => {
console.log("帰宅した");
callback(); // "帰宅した"あとに`callback`を実行する。callbackについては知らない。
}

goHome(playGame); // callbackとして"ゲームをする"処理を渡す

このコードをブラウザで貼り付けて実行すると

帰宅した
ゲームする

と出力されます。

遅延関数をコールバックで表現する

const wait = (ms, callback) => {
setTimeout(() => {
callback(); // 指定`ms`後にコールバックを実行する
}, ms);
}

パット見良さそうです。それでは、1秒後に"A"、その2秒後に"b"、その3秒後に"c"を出力させる処理を書くと

console.log("start");
wait(1000, () => {
console.log("A");
wait(2000, () => {
console.log("B");
wait(3000, () => {
console.log("C");
});
});
});

となります。ピンとくるまでじっくりと観察してみてください。処理がどんどん増えるたびに階層(コードブロックのネスト)が深くなります。 これは流石に可読性が低くなるため、Promiseasync/awaitが発展していきました(チョット雑)。

長くなってしまったのでここまでにします。

参考