危険!JavaScriptでループするときの注意点

こんにちは。フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、技術の栄枯盛衰が激しいプログラムの世界に長年携わっていると、数年前まではよく使っていたものが今から考えると「あれは一発屋だったな・・・好きだったのに」というものが多く存在していたりします。

※ 個人的な話で言うと「検索プロバイダー」という、ブラウザに独自の検索窓を用意することができる機能は気に入っていたのですが、今では見かけることは少なくなってしまいました😓

そして、ここ数年の開発状況で特に変化があったなと思うのは「データ送信の方法」です。つまり、以前は通常のHTTPアクセスでPOST送信していたものを、現在ではほぼすべてaxiosを使ってAjax送信に切り替えています。

メリットとしてはアクセス速度が上がるというのもありますが、開発者としてはサイト構築する効率を向上させることができるというのが大きいです。

ちなみにそんなAjax送信ですが、この間for()を使ってループさせながらアクセスする場合に1点気をつけないといけない場合があることに気がつきました。

今回はそんな(私がハマった)Ajaxのワナに関する記事をお届けします!
ぜひ皆さんのお役にたてると嬉しいです。

問題が発生する場合

例えば以下のコードを見てください

for(var i = 1 ; i <= 10 ; i++) {

    var url = 'http://l58.test?loop='+ i;
    axios.get(url)
        .then(function(){

            console.log(i +'番目のアクセスが完了しました')

        })

}

内容としては、よくみるfor()ループの中でaxiosを呼んでGET送信をしているシンプルなものですね。

では、このコードを実行するとどうなるかGoogle Chromeの開発者ツールを使ってコンソール表示してみましょう。

ん!??

ループでは変数iは1〜10の数字が入ってくるはずなのに全て11という結果になってしまいました。

そうです。
これがループさせるときに注意しなければいけない点です。

つまり、Ajaxは非同期通信のためループの変数はその都度書き換えられてしまい、最終的に11になった時点でconsole.log()が呼ばれることになってしまったわけです。

ちなみにアクセスされるURLも以下の通りすべて同じになってしまいます。

これでは思い通りの結果を取得することはできません。

解決する方法は?

ES6を使う(IE 11は除く)

もしIE 11は考慮しなくても良いなら新しいJavaScriptの書き方「ES6(もしくは、ES2015)」を使ってシンプルにこの問題を解決することができます。

for(let i = 1 ; i <= 10 ; i++) {

    var url = 'http://l58.test?loop='+ i;
    axios.get(url)
        .then(function(){

            console.log(i +'番目のアクセスが完了しました')

        })

}

一見先ほどと同じコードのように見えますが、for()の中のvarletに変更しています。

こうするだけで、変数iはループごとに区別されることになるので、それぞれの数字が適用されるわけですね。

ではコンソールをチェックしてみましょう。

はい!
思い通りにいきました。

※ ただし、この例ではPHP側でsleep()を使って順番が並ぶように調整しています。

即時関数をつかう

ES6を使うのはとてもシンプルでいいのですが、おそらく「んー、でもIEで使えないなら問題アリですよね・・・」という場合もあるでしょう。

そういう場合は少し手間にはなりますが「即時関数」を使って解決することができます。

では実際のコードを見てみましょう。

for(var i = 1 ; i <= 10 ; i++) {

    (function(i){

        var url = 'http://l58.test?loop='+ i;
        axios.get(url)
            .then(function(){

                console.log(i +'番目のアクセスが完了しました')

            })

    })(i);

}

最初のコードと違うのはAjax部分が即時関数の中に入っていることです。

こうすることで、変数iが関数の変数として保持されることになり、結果として予想していたとおりの機能を実装することができるようになります。

ちなみに

今回はAjaxを使った例でループの注意点を説明しましたが、今回の現象はAjaxだけでなく、例えば以下のようにFileReaderを使って複数ファイルの読み出しをする等の場合にも同じ状況になります。(即時関数を使って解決しています)

for(var i = 0 ; i < files.length ; i++) {

    var file = files[i];
    var reader = new FileReader();
    reader.onload = (function(i) {
        return function(e) {

            // ここで何か

        }
    })(i);
    reader.readAsDataURL(file);

}
開発のご依頼お待ちしております
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ

おわりに

まだまだブログを書く人間としては新米ですが、このブログの記事を書いているとホントによく書くのが「IEでは対応してません」という文章。

最近、マイクロソフトが独自のレンダリングエンジンをやめてChromiumベースでブラウザを一新するというニュースが流れましたが、これはEdgeであってIEではありません。(いや、問題はそっちじゃないでしょ!と大多数がツッコミをいれたことでしょう😭)

IEもChromiumベースにしろとは言いませんが、せめてES6ぐらいはきちんとサポートしてほしいもんだとつくづく感じてしまいました。(IEの功績は誰もが認めるもので、感謝はしてますけどね)

ということで今回はJavaScriptでループするときに注意するべき点の記事をお届けしました。皆さんもお気をつけくださいね。

ではでは〜♪

このエントリーをはてなブックマークに追加       follow us in feedly