JavaScript(Node.js) で sleep() アラカルト


JavaScript沢志保「sleep() がないんです!」

JavaScript を書く

「この処理が終わったら 1 秒待ってから次の処理に行きたいな」

sleep(1);

ReferenceError: sleep is not defined

(´・ω・`)

ってなったことがあるのはぼくだけでしょうか……。

Ruby にも PHP にも C++ にもあるのに JavaScript にはない sleep()
そもそも需要がありそうなのに実装されてないということは暗に使うなと言われているのに等しいような気もするのですが、それでも使いたい人はたくさんいるようで、ググるとたくさん実装方法が出てきます。

そこで今回は様々な方法で sleep() を実装して、JavaScript 歴1ヶ月ちょいのド素人なりに利点と欠点を考えてみました。

前置き

動作環境

Node.js v7.6.0

sleep() の動作

今回は多数のプログラミング言語に実装されている sleep() と同様、「指定された秒数だけプログラムを一時停止する関数」を Node.js で実装します。
また、引数にはミリ秒を指定することとします。(例: sleep(2000) -> 2秒一時停止する)

プログラムの内容

  1. 「フラ」を出力
  2. sleep(1000) で1秒一時停止
  3. 「イド」を出力
  4. sleep(2000) で2秒一時停止
  5. 「チキーン!」を出力して終了

つまりこれ。

1. setTimeout() だけ使って書いてみる

まずは一番オーソドックスに。

setTimeout()版
'use strict';

console.log('フラ');
setTimeout(() => {
    console.log('イド');
    setTimeout(() => {
        console.log('チキーン!');
    }, 2000);
}, 1000);

メリット

  • 5つの中で一番簡単

デメリット

  • 入れ子なので処理の流れがわかりづらい
  • ネストが深くなるリスク

2. Date() を使って書いてみる

こちらのブログ記事で紹介されていたものです。

Date()版
'use strict';

console.log('フラ');
sleep(1000);
console.log('イド');
sleep(2000);
console.log('チキーン!');

function sleep(time) {
    const d1 = new Date();
    while (true) {
        const d2 = new Date();
        if (d2 - d1 > time) {
            return;
        }
    }
}

メリット

  • 簡単
  • 処理の流れが同期的に書ける

デメリット

  • Date() をひたすら生成し続けるのはどうなんだろう……

3. Promise だけ使って書いてみる

ES6(ES2015) から実装された Promise を使ってみます。

Promise版(良くない例)
'use strict';

Promise.resolve()
.then(() => {
        console.log('フラ');
        sleep(1000)
            .then(() => {
                console.log('イド');
                sleep(2000)
                    .then(() => {
                        console.log('チキーン!');
                        Promise.resolve();
                    })
            })
    });

function sleep(time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, time);
    });
}

メリット

  • 処理の流れが(ある程度は)同期的に書ける

デメリット

  • ネストの闇が深すぎる(VSCode で自動整形した結果)
    • 自分の実装が下手なだけな気がするので指摘お願いします……
  • IE だと動かない(MDN)

コメントにてご指摘をいただきました、ありがとうございます。

Promise版(良い例)
'use strict';

console.log('フラ');
sleep(1000).then(() => {
    console.log('イド');
    return sleep(2000);
}).then(() => {
    console.log('チキーン!');
});

function sleep(time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, time);
    });
}

4. async モジュールを使って書いてみる

Node.js で人気の async モジュールを使います。

asyncモジュール版
'use strict';
const async = require('async');

async.series([
    (callback) => {
        console.log('フラ');
        callback(null);
    },
    (callback) => {
        sleep(1000, () => callback(null));
    },
    (callback) => {
        console.log('イド');
        callback(null);
    },
    (callback) => {
        sleep(2000, () => callback(null));
    },
    (callback) => {
        console.log('チキーン!');
        callback(null);
    }
],
    () => { }
);

function sleep(time, callback) {
    setTimeout(() => {
        callback(null);
    }, time);
}

メリット

  • 処理の流れが同期的に書ける

デメリット

  • Node.js 依存
  • いちいちコールバックするのめんどい

5. Promise と async/await で書いてみる

Node v7.6.0 から V8 が v5.5 になりました。(Change Log)
つまり --harmony しなくても async/await が使える!やったー!

参考: Node.js v7.6.0がリリースされたのでasync/awaitで遊んでみた / Qiita

Promise+async/await版
'use strict';

!async () => {
    console.log('フラ');
    await sleep(1000);
    console.log('イド');
    await sleep(2000);
    console.log('チキーン!');
}();

function sleep(time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, time);
    });
}

メリット

  • 簡単
  • 処理の流れが同期的に書ける

デメリット

  • IE はおろか Edge と Safari でも動かない(MDN)

5つ作ってみて

  1. setTimeout() オンリー
    → ちゃちゃっと簡単なものを作りたい時、どのブラウザでも動かしたいとき。

  2. Date()
    → 同上だけどこっちの方が同期的に書けるのでおすすめ。

  3. Promise オンリー
    → うーん……。いまいち利点が見つからない。

  4. async モジュール
    → Node.js 限定。v7.6.0 以前で動いてるなら。

  5. Promise + async/await
    → ES2017 / v7.6.0 以降の Node.js を使えるなら迷わずこれ。

実は元々「async/await ヤッター!」と言いながら 5. を一番最初に作ってみたのがきっかけの記事だったのですが、各ブラウザの対応状況やコードの可読性を考えると今のところは 2. が最適解なのかなぁ……と思いました。
ES2017 の仕様が Edge や Safari でも使えるようになって、async/await で書けるようになるのが待ち遠しいです。

完全に素人目線の記事なので、ご指摘ありましたら何卒よろしくお願いします……。