JavaScript
promise
BaaS
BaaS@rakuza

BaaS@rakuzaのCordovaプラグインをPromiseに対応させる

More than 1 year has passed since last update.

BaaS@rakuzaのCordovaプラグイン(RKZClient)の各関数は非同期処理のため、引数に成功時・失敗時のコールバック関数を受け取るようになっています。
そのため、A処理をしてからB処理をする場合に、処理がネストしてソースコードが見づらくなってしまいます。(いわゆる、コールバック地獄)
エラー処理が何度も出てくるし、微妙・・

MainController.js
RKZClient.setTenantKey(TENANT_KEY, function() {
  // 初期化が終わったら、ユーザーを登録
  var userData = {};
  RKZClient.registUser(userData, function(userData) {
    // ユーザー登録が終わったらプッシュ通知の初期化(デバイストークンの登録)
    setupPushNotification(userData.user_access_token);
  }, function () {
    // エラー処理
  });
}, function(error) {
  // エラー処理
});

引数にコールバック関数を受け取る方法ではなく、Promiseを返す様にしたら解決します。
以下のようなイメージ
エラー処理も1つにまとまり、すっきり

MainController.js
setTenantKey().then(function() {
  // 初期化が終わったら、ユーザーを登録
  var userData = {};
  return registUser(userData);
}).then(function (userData) {
  // ユーザー登録が終わったらプッシュ通知の初期化(デバイストークンの登録)
  setupPushNotification(userData.user_access_token);
}).catch(function(error) {
  // エラー時にアラートでエラー内容を表示します
  alert(JSON.stringify(error, null, ' '));
});

function setTenantKey() {
  var deferred = $q.defer();

  RKZClient.setTenantKey(TENANT_KEY, function() {
    deferred.resolve();
  }, function(error) {
    deferred.reject(error);
  });

  return deferred.promise;
}

function registUser(userData) {
  var deferred = $q.defer();

  RKZClient.registUser(userData, function(userData) {
    deferred.resolve(userData);
  }, function(error) {
    deferred.reject(error);
  });

  return deferred.promise;
}

ただ、RKZClient呼び出すごとにPromiseでラップする処理を書くのは面倒です。
そこで、RKZClient自体をラップしてみます。ついでに、PromiseはES2015のオブジェクトを使います。

RKZClientPromise
(function (global) {
  global.document.addEventListener('deviceready', onDeviceReady);

  function onDeviceReady() {
    var originalClient = global.RKZClient;

    // RKZClientの各関数をProxy関数で上書き
    // (RKZClientのプロトタイプのプロパティのみ処理していることに注意)
    Object.keys(Object.getPrototypeOf(originalClient)).filter(function (key) {
      return typeof originalClient[key] === 'function';
    }).forEach(function (key) {
      console.log(key);
      originalClient[key] = createProxy(originalClient[key]);
    });

    function createProxy(originalFunc) {
      return function () {
        var args = Array.prototype.slice.call(arguments);
        var handlers = args.filter(function (arg) {
          return typeof arg === 'function';
        });
        // ハンドラーが引数にある場合は、元の関数を実行
        if (handlers.length != 0) {
          return originalFunc.apply(this, args);
        }
        // ハンドラーが引数にない場合は、Promiseを返す
        return new Promise(function (resolve, reject) {
          args.push(function () {
            resolve.apply(this, arguments);
          });
          args.push(function () {
            reject.apply(this, arguments);
          });
          originalFunc.apply(this, args);
        });
      };
    }
  }
})(window);
index.html
<script>
  ons.bootstrap('myApp');
</script>
<script src="js/RKZClientPromise.js"></script> <!-- MainController.jsより先に読み込む -->
<script src="js/controllers/MainController.js"></script>

これで、RKZClientの各関数がPromiseを返す様になります。

MainController.js
RKZClient.setTenantKey(TENANT_KEY).then(function() {
  // 初期化が終わったら、ユーザーを登録
  var userData = {};
  return RKZClient.registUser(userData);
}).then(function (userData) {
  // ユーザー登録が終わったらプッシュ通知の初期化(デバイストークンの登録)
  setupPushNotification(userData.user_access_token);
}).catch(function(error) {
  // エラー時にアラートでエラー内容を表示します
  alert(JSON.stringify(error, null, ' '));
});

Promiseに対応していないライブラリがあれば、同じ様にラップしてみても良いかもしれません。
※公式が対応してくれるのが一番ですが