JavaScriptのfor分で順次APIを叩いてレスポンスを表示しようと思ったら、緩いスコープの罠にハマってしまいましたので、ご報告のブログです。
このソースコードの問題を見つけてください。
このソースコードにはある問題があります。
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を叩き、その結果を表示するなどしようという目的なのですが、結果はこうなります。
デモ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がそれぞれ別のスコープの変数になり、互いに干渉しません。
デモ2
結果として、ちゃんと非同期処理のコールバック処理で別の変数を処理することができました。
コメント