JavaScriptのfor文で非同期処理をする場合はスコープに気をつけろ!

JavaScriptのfor分で順次APIを叩いてレスポンスを表示しようと思ったら、緩いスコープの罠にハマってしまいましたので、ご報告のブログです。
スクリーンショット 2013-08-01 19.50.01

このソースコードの問題を見つけてください。

このソースコードにはある問題があります。

var fruits = ['apple', 'banana', 'orange'];
for (var i in fruits) {
	var fruit = fruits[i];
	$('body').append('

loading ' + fruit + '...

'); $.ajax({ url : './details_api?fruit=' + fruit, complete : function(response) { $('body').append('

complete loading ' + fruit + '.

'); } }); }

タイトルに答え書いてあるので、問題になっていないですが・・・。

JavaScriptにブロックスコープはない

このプログラムはfor文を回しながら、順番にAPIを叩き、その結果を表示するなどしようという目的なのですが、結果はこうなります。
スクリーンショット 2013-08-01 19.33.42
デモ1
最後に、fruit変数に、orangeが上書きされてから、非同期処理のレスポンスが返るので、すべての表示がorangeになってしまいます。
最近JavaとObjective-Cばかり書いていて、ブロックスコープはあって当然と思って書いていたらハマリました。

JavaScriptのスコープについて覚えておくべきこと

冷静になって、「Javascriptの変数スコープについて少しだけまとめてみた」を読みました。
結局「JavaScriptには、グローバルスコープと関数スコープしかない」ということで、ローカル変数は関数スコープなのでその関数の頭に宣言されているのと同じということです。「関数内ローカル変数宣言は全部先頭に移動しちゃう法則」という表現がわかりやすかったです。

ブロックの代わりに関数になっていれば良い

Underscore.jsに、eachという関数がありまして、これはいわゆるfor eachを簡単に使えるようにしたものなのですが、これはブロックではなく関数でループ内の処理を記述します。

var fruits = ['apple', 'banana', 'orange'];
_.each(fruits, function(fruit) {
	$('body').append('

loading ' + fruit + '...

'); $.ajax({ url : './details_api?fruit=' + fruit, complete : function(response) { $('body').append('

complete loading ' + fruit + '.

'); } }); });

通常のfor文がブロックで記述するところを関数で受けているので、fruitがそれぞれ別のスコープの変数になり、互いに干渉しません。
スクリーンショット 2013-08-01 19.33.54
デモ2
結果として、ちゃんと非同期処理のコールバック処理で別の変数を処理することができました。

コメント

タイトルとURLをコピーしました