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('<p>loading ' + fruit + '...</p>');
	$.ajax({
		url : './details_api?fruit=' + fruit,
		complete : function(response) {
			$('body').append('<p>complete loading ' + fruit + '.</p>');
		}
	});
}

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

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('<p>loading ' + fruit + '...</p>');
	$.ajax({
		url : './details_api?fruit=' + fruit,
		complete : function(response) {
			$('body').append('<p>complete loading ' + fruit + '.</p>');
		}
	});
});

通常のfor文がブロックで記述するところを関数で受けているので、fruitがそれぞれ別のスコープの変数になり、互いに干渉しません。

スクリーンショット 2013-08-01 19.33.54

デモ2

結果として、ちゃんと非同期処理のコールバック処理で別の変数を処理することができました。

About katty0324

Leave a Reply

Scroll To Top