Web Developer's Struggle Memories

日々の業務から思ったこと、学んだことを書き連ねていきます。

Riot.jsでAjaxを使った非同期通信について

はじめに

現在あるプロジェクトで、JavaScript界隈では一時期話題になった(*1Riot.jsで開発をしている。DBとのデータのやり取りを全てAPI化しているため、APIに非同期に通信しているのだが、ちょっとコケたので(*2)備忘録として書き残す。

開発環境など

  • Riot.jsのバージョンは2.4.1
  • AjaxにはjQueryを使用 ※$.ajax()のこと

本題のコード

とりあえず.jsファイル、.tagファイルを見てみよう。今回はペン(ボールペンやシャーペンのことね)のメーカー情報を取得するものを例にする。

hoge.js

/**
 * 非同期通信のまま返り値を受け取るメソッド
 * 
 * @param {object} params    パラメータオブジェクト
 *        {string} url       コントローラの指定
 *        {string} method    POSTかGET
 *        {string} data_type jsonかxml
 *        {object} datas     keyと値の組み合わせ
 * @return void
 */
function get_list(params) {
    return $.ajax({
        type    : params.method,
        url     : params.url,
        dataType: params.data_type,
        data    : params.query,
    });
}

// APIサーバのURL
var ajax_url = "192.168.99.100:81/";   // 開発中なのでローカルで

・pen_list.tag

<pen-list>
   <!-- layout -->
   <div>
      <ul>
         <li each="{ val, id in maker_list }">
            <label>{ val }</label>
            <input type="checkbox" name="fuga_{ id }" value="ballpoint[]">
         </li>
      </ul>
   </div>

   <!-- javascript -->
   <script>
   // 初期マウント時にはない変数を定義
   opts.maker_list = {};

   this.on('mount', function() {
      // ajax用にパラメータを設定
      var params = {                                         
         url: ajax_url + '/makers',
         method: 'GET',
         data_type: 'json',
         query: {
            pen_type: 'ballpoint',   // 今回はボールペン
            lang: 'ja'
         }                                                          
      };

      // メーカーのリストを取得
      get_list(params)
         .done(function(result) {
            opts.maker_list = result;
            riot.update();   // ページの更新
         })
         .fail(function(e) {
            alert('メーカーの取得に失敗しました');
         });
   });
   </script>
<pen-list>

pen-listタグがマウントされた時にAPIサーバにメーカーリストのリクエストをGETで投げ、 戻り値でメーカー名をcheckbox付きでリスト表示する。

しかし、このままだと動かない。非同期通信だと値が戻ってくる前に処理が先に進むため、riot.update()が意味を成さない。 対策としてどうしようか思いつかなく、突貫でsetIntervalを使用…

以下コード。

// とりあえずintervalで反映
var timer = setInterval(function() {
   if (opts.maker_list !== '') {
      riot.update();
      clearInterval(timer);
   }
}, 300);

300msずつopts.maker_list変数に戻り値がセットされている確認し、セットされていたらpen_listタグのアップデートを実行。 本当はこのページに遷移する前にリクエストを投げデータを取得し、それを持ってmountすれば済む話なのだが、このページに直接アクセスされることもあるので…

2016/06/17 追記

@bj1024さん、@otoan52さんからtwitter上でコメントを、phiaryさんからコメント・コード例をいただき、上記のコードのjs部分を以下のように修正しました。

// 初期マウント時にはない変数を定義
var self = this;
self.maker_list = [];

// ajax用にパラメータを設定
var params = {
   url: ajax_url + '/makers',
   method: 'GET',
   data_type: 'json',
   query: {
      pen_type: 'ballpoint',   // 今回はボールペン
      lang: 'ja'
   }                                                          
};

// メーカーリストの取得
get_list(params)
   .done(function(result) {
      self.maker_list = result;
      self.update();   // ページの更新
   })
   .fail(function(e) {
      alert('メーカーの取得に失敗しました');
   });
});

修正点は、this.on('mount')を削除、頭にvar self = this;とし、self.update();にした。 今までなぜ動かなかったのか、原因の完全な究明はできてないが(*3)、 動くようになった。情報頂いたお三方、本当にありがとうございました!!

終わりに

マウントされた.tag内でAjax使って非同期通信すると必ずここにぶち当たるので対策しないといけないのだが、いかんせん知識が足りなさすぎる。 どなたかナイスなアイディアをお持ちの方!是非是非情報をお待ちしております!!(切実)

Riot.jsそのもののコードをちゃんと読もう。(必須)

注釈

*1:日本では全く名前を聞かないのは何故だろう?

*2:根本的には解決していない…

*3:setIntervalも非同期通信なのでなぜこれで動き、done()では動かないのか分からない…