0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【async/await版】JavaScriptでループ中にスリープしたい。それも読みやすいコードで

Last updated at Posted at 2020-04-30

JavaScriptでループ中にスリープしたい。それも読みやすいコードでという5年くらい前の記事を読み、コメント欄に、今だったらasync/awaitを使ってこうできるよーという例が挙げあられていたんですが、パッと見て理解できなかったのでメモとして残しておきます。

理解するのに役立ったのは、JavaScript Promiseの本の1.2.1. Promise workflow5.3. await式です。
setTimeoutの挙動をgifで説明している♻️ JavaScript Visualized: Event Loopも参考になりました。

今回は、1000ミリ秒ごとにランダムな数字を出力する処理を10回繰り返すコードを目標とします。

以下は、動かないけど実現したいコードです。


for (let i = 0; i < 10; i++) {
    console.log(Math.random())
    sleep(1000);
}

async/awaitでやってみる

上のコードは、以下のように async/await を用いて実現できます。

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

async function main() {
  for (let i = 0; i < 10; i++) {
    console.log(Math.random());
    await sleep(1000);
  }
}

main()

ちょっとした解説

  • await式では非同期処理を実行し完了するまで、次の行(次の文)を実行しない
    => sleep(1000)の実行が終わるまでfor文の次の周期にはいかない(1000ミリ秒停止する)。

  • sleep関数はPromiseを返している
    => sleep(1000)の実行が終わる = PromiseがFulfilledになる = promiseオブジェクトがsetTimeoutでmilliseconds(1000ミリ秒)後にresolveされる

new Promise(resolve => setTimeout(resolve, milliseconds)); ってなんだ?

違和感があったのは、await式の返り値がないことです。
例えば、以下のコードだとawait式の返り値は sccess となります。

function sleep(milliseconds) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve("success")
        }, milliseconds)
    });
}

async function main() {
  for (let i = 0; i < 10; i++) {
    console.log(Math.random());
    let value = await sleep(1000);
    console.log(value) // sccess
  }
}

main()

しかし今回は時間を止めるだけなのでPromiseは値をresolveする必要はないため、ちょっと違和感のあるコードになっていました。

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

async function main() {
  for (var i = 0; i < 10; i++) {
    console.log(Math.random());
    let value = await sleep(1000);
    console.log(value) // undefined
  }
}

main()

async/awaitを使わずPromiseだけでやってみる

特に意味はないですが勉強になりそうなので。

Promiseのthenを復習する

参考 とほほのPromise入門

100の2倍を求める非同期関数の使用例です。

function func1(data, callback) {
  setTimeout(() => {
    callback(data * 2);
  }, 1000);
}

function sample_callback() {
  func1(100, function(value) {
    console.log(value); //200
  });
}

sample_callback();

この時点でつまずきそうですが、関数そのものを引数にして渡してるだけです。
これをPromiseで書き換えると以下になります。

function func2(data) {
  return new Promise(function(resolve) {
    setTimeout(function() {
      resolve(data * 2);
    }, 1000);
  });
}

function sample_promise() {
  func2(100).then(data => {
    console.log(data); // 200
  });
}

sample_promise();

Promiseが1000ミリ秒後に resolve(data * 2) と解決されて then の onFulfilled に設定された関数に data * 2 という値を渡します。
awaitの代わりにthenを使っているイメージを持っているのですがあってるでしょうか???

promise chain

以下のコードでは、promise chain をつなげて処理をしています。
returnされてきた func2(data)に対してさらに then で処理することを繰り返しています。
このコードでは、一秒間隔で 200, 400, 800 が出力されます。

function func2(data) {
  return new Promise(function(resolve) {
    setTimeout(() => {
      resolve(data * 2);
    }, 1000);
  });
}

function sample_promise3() {
  func2(100)
    .then(data => {
      console.log(data);
      return func2(data); // 200
    })
    .then(data => {
      console.log(data);
      return func2(data); // 400
    })
    .then(data => {
      console.log(data); // 800
    });
}

sample_promise3();

本題

ここまで理解できたのでasync/awaitを使わずPromiseだけでやってみます。


let myPromise = main(1000);

for (let i = 0; i < 3; i++) {
  myPromise = myPromise.then(num => {
    console.log(num);
    return main(1000);
  });
}

function main(milliseconds) {
  return new Promise(function(resolve) {
    setTimeout(function() {
      resolve(Math.random());
    }, milliseconds);
  });
}

for文の中身が少しややこしくて、

for (var i = 0; i < 3; i++) {
  myPromise = myPromise.then(num => {
    console.log(num);
    return main(1000);
  });
}

は、以下と(ほぼ)同義です。

myPromise
  .then(num => {
    console.log(num);
    return main(1000);
  })
  .then(num => {
    console.log(num);
    return main(1000);
  })
  .then(num => {
    console.log(num);
  });

もっと分かりやすくすると、

for (var i = 0; i < 3; i++) {
  myPromise = myPromise.then(hoge)
}

は、以下と同義です。


myPromise
  .then(hoge)
  .then(hoge)
  .then(hoge);

まとめ

Promiseのみを使う


let myPromise = main(1000);

for (let i = 0; i < 3; i++) {
  myPromise = myPromise.then(num => {
    console.log(num);
    return main(1000);
  });
}

function main(milliseconds) {
  return new Promise(function(resolve) {
    setTimeout(() => {
      resolve(Math.random());
    }, milliseconds);
  });
}

async/awaitを使う

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

async function main() {
  for (let i = 0; i < 3; i++) {
    console.log(Math.random());
    await sleep(1000);
  }
}

main()

async/awaitの方がだいぶ直感的ですね!!
「Promiseのみを使う」のlet myPromise = main(1000);で最初にpending状態のPromiseを定義するのも違和感がありますね。

半日かけでじっくりやったので、Promiseおよびasync/awaitが分かった気になってます。
間違いがあればご指摘お願いします!!

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?