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

Javascript Ajax 非同期処理を含む繰り返し処理での実行順序について

More than 1 year has passed since last update.

はじめに

Javascriptで非同期処理を実装する場合、
何も対策を行わず実装してしまうと思いもよらない動作をしてしまうことがあります。

今回はその例と対策を紹介します。

前提条件

HTML

以下のような3つのプルダウンを設置します

言語
<select name="sel0" id="sel0">
  <option value="0">--選択して下さい--</option>
  <option value="1">日本語</option>
  <option value="2">English</option>
</select>

スポーツ
<select name="sel1" id="sel1"></select>
食べ物
<select name="sel2" id="sel2"></select>

Ajax

以下のようなレスポンスが返ってくるAPIを作成します。

//ajax_url1
// param: id => 1
{options: [[1,'サッカー'], [2,'野球'].....]}
// param: id => 2
{options: [[1,'Soccer'], [2,'Baseball'].....]}

// ajax_url2
// param: id => 1
{options: [[1,'かけうどん'], [2,'ぶっかけうどん'].....]}

// param: id => 2
{options: [[1,'Udon Noodles'], [2,'Bukkake Noodles'].....]}

// by google翻訳

期待する結果

今回は、
「言語」のchangeイベントで「スポーツ」、「食べ物」に言語に適したoptionをセットする
を目的とします。(いやそれどこで使うねんというツッコミは野暮)

言語で「日本語」を選択した場合の期待する結果は以下です。

<select name="sel0" id="sel0">
  <option value="0">--選択して下さい--</option>
  <option value="1">日本語</option>
  <option value="2">English</option>
</select>

スポーツ
<select name="sel1" id="sel1">
  <option value="1">サッカー</option>
  <option value="2">野球</option></select>
食べ物
<select name="sel2" id="sel2">
  <option value="1">かけうどん</option>
  <option value="2">ぶっかけうどん</option>
</select>

それではJS部分を実装してみましょう。

実行順序を考慮していない実装

実行順序のことを全く頭に入れずに実装します。

Javascript

$(document).on('change', '#sel0', function() {
  var   val = $(this).val(); // 「言語」で選択した値  
  for(i = 0 ; i < 2 ; i++){
    var select_elm = $('#sel' + i); // selectエレメント
    $.ajax({
      type: 'GET',
      url: 'ajax_url' + i,
      data: { id: val },
      success: function(result) {
        // selectエレメントにoptionを追加
        $.each(result.options, function() {
          select_elm.append($('<option>').val(this[1]).text(this[0]));
        });
      }
    });
  }
});

いかがでしょうか?
正常に動作しそうじゃないですか?

結果

しかし、このような実装だと正しく動作したりしなかったりするのです。
Aさんが動かすと正常に動作したり、
Bさんが動かすと以下のような結果になることもあります。

<select name="sel0" id="sel0">
  <option value="0">--選択して下さい--</option>
  <option value="1">日本語</option>
  <option value="2">English</option>
</select>

スポーツ
<select name="sel1" id="sel1"></select>
食べ物
<select name="sel2" id="sel2">
  <option value="1">かけうどん</option>
  <option value="2">ぶっかけうどん</option>
</select>

うそっしょ?

なぜそのようなことが起きるのか

JSのコードはなんの対応もなしだとAjaxのレスポンスを待ってくれません。
forだけが先に回ってajax_url1(スポーツ)のレスポンスが返ってくる頃にはselect_elmにはsel2のエレメント(食べ物)が代入されているためこのような事象が起こるのです。

実行順序を考慮した実装

クールではないのですが以下のように対策できます。

Ajax

//ajax_url1
// param: id => 1, select_id => 1
{options: [[1,'サッカー'], [2,'野球'].....], select_id: 1}
// param: id => 2, select_id => 1
{options: [[1,'Soccer'], [2,'Baseball'].....], select_id: 1}

// ajax_url2
// param: id => 1, select_id => 2
{options: [[1,'かけうどん'], [2,'ぶっかけうどん'].....], select_id: 2}

// param: id => 2, select_id => 2
{options: [[1,'Udon Noodles'], [2,'Bukkake Noodles'].....], select_id: 2}

// by google翻訳

Javascript

$(document).on('change', '#sel0', function() {
  var   val = $(this).val(); // 「言語」で選択した値  
  for(i = 0 ; i < 2 ; i++){
    $.ajax({
      type: 'GET',
      url: 'ajax_url' + i,
      data: { id: val, select_id: i },
      success: function(result) {
        var select_elm = $('#sel' + result.select_id)  // selectエレメント
        // selectエレメントにoptionを追加
        $.each(result.options, function() {
          select_elm.append($('<option>').val(this[1]).text(this[0]));
        });
      }
    });
  }
});

強引な実装ですが、
appendするselectエレメントをAjaxのsuccess内で定義することで対策ができます。

まとめ

JSでAjaxを使用する時は、実行順序を気にする。(小並感)

気になる

以下の機能を使うともっとクールな実装ができそうです。
興味がある方がググってどうぞ!

  • jQuery.Deferred
  • .when().done()

いい記事を見つけました
JavaScriptがちょっとわかる人向けの、Ajaxの話。

ブログ記事で見たい方はこちら → Javascript Ajax 非同期処理を含む繰り返し処理での実行順序について

ayies128
株式会社illustrious CTO。 EC業務を色々な角度から効率化できるように日々システム開発を行っています。 システム開発の話から飲みに行こうって話までなんでもどうぞ。 うどんとお酒と #UVERWorld が大好きです。 世界で売るためのECインフラサービス「lismoa」の開発運営を行なっています。 https://lismoa.com/
http://nabesys.com/
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
No 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
ユーザーは見つかりませんでした