Help us understand the problem. What is going on with this article?

javascriptのsetTimeoutが即実行されてハマった

More than 1 year has passed since last update.

sleep処理を実装するため、setTimeoutで再帰っぽく書いてたらハマったのでメモ

一見すると問題ないがダメなコード

次のコードは問題ないように見える。
出力もされ、期待通り動いているようだが?

var hoge = function(str) {
  console.log(str)
}

//ダメな書き方
setTimeout(hoge('すぐ実行されてしまう'),4000)

しかし、hoge()がすぐに実行されている事に気づく

こうすると良く分かる。

var hoge = function(str) {
  console.log(str)
}

setTimeout(hoge(''),3000)
setTimeout(hoge(''),2000)
setTimeout(hoge(''),1000)

「あ い う」 という順番で出て欲しいが、
「う い あ」 で出てしまう

ちゃんと動く3つの方法

var hoge = function(str) {
  console.log(str)
}


//良い
setTimeout(hoge,2000,"第三引数に入れてあげる")
setTimeout("hoge('これも可能だが非推奨だから注意')",2000)
setTimeout(function(){console.log('ぐぐると一番良く見る奴')},2000)

順番に解説していく

1 第三引数を使う

setTimeoutの第三引数を使う事で、中の関数に引数を渡すことが出来る

setTimeout(hoge,2000,"あああ")

引数を2つ渡したい場合は第三引数、第四引数 … とする。
hoge() ではなく hogeな点に注意。

2 文字列として渡す

なんと、こんな方法でも動く。

setTimeout("hoge('あああ')",2000)

これが動くのは不思議だが。
https://developer.mozilla.org/ja/docs/Web/API/WindowTimers/setTimeout
を見ると

関数の代わりに文字列を含める代替構文も許容されており、タイマーが満了したときに文字列をコンパイルして実行します。
eval() の使用にリスクがあるのと同じ理由で、この構文は 推奨しません。

setTimeoutはevalみたいに文字列をコードとして解釈するらしい。
非推奨と書いてあるので、あんまりおすすめはしない

3 無名関数を使う

ググると一番よく出てくる方法

setTimeout(function(){console.log('あああ')},2000)

(無名関数 という名称についてはいろいろありますが一般的にそう呼ばれてるのでその話はここではしません)

なんで最初のじゃ動かないのか

setTimeout(hoge('すぐ実行されてしまう'),4000)

では、hogeがまず評価(実行)され、setTimeoutはその結果をwait後に実行する。
hoge('すぐ実行されてしまう')の結果はundefinedのため、

setTimeout(undefined,4000)

を実行している事になる。
だから何も起こらない。

良く考えれば当たり前だが、とても嵌る箇所だと思う。
文法的には間違っていないので厳格モードでも動くし、
waitがかかってないだけで一見動いてはいるのでぼーっとしていると気づかない。

最初みたいな感じで書きたい

//こういう風にもかける
var hoge2 = function(str) {
  return function(){console.log(str)}
}
var hoge3 = str => {
  return () => console.log(str)
}
setTimeout(hoge2('従来のjs'),2000)
setTimeout(hoge3('ES6'),2000)

waitを入れつつ、同じ関数を10回実行する

ダメ

function counter() {
  var count = 0;
  return function re () {
    if (count < 10 ) {
      count++
      console.log(count)
      setTimeout(re(),100)
    }
  }
}
var count = counter();

setTimeout(count(),100)

良い

function counter() {
  var count = 0;
  return function re () {
    if (count < 10 ) {
      count++
      console.log(count)
      setTimeout(re,100)
    }
  }
}
var count = counter();

setTimeout(count,100)

ES6からはPromiseが使えるため、普段なかなか利用しないsetTimeoutであるが
すごくハマった

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away