スタッフブログ Staff Blog

JavaScriptとネイティブの連携にPromiseを使用した事例の紹介

弊社では目下、Movable Type / PowerCMSで運用中のウェブサイトを10分でアプリ化する「Apliko(アプリコ)」の新テーマ開発を行っています。

開発の中でJavaScript(HTMLで作成した画面)からネイティブの機能を呼ぶ必要があり独自のJavaScriptライブラリを開発しました。その際、非同期処理を分かりやすく記述できる「Promise」を利用しましたのでご紹介したいと思います。

処理の流れ

  1. apliko.storage.get()でネイティブに保存しているユーザー名を取得し、フォームのID欄にセットする
  2. apliko.securedata.get()でユーザー名を基にネイティブに保存しているパスワードを取得し、フォームのパスワード欄にセットする

コールバックを利用して書いた場合

昔からある「コールバック」で書くと以下のようになります。

(function ($) {
  var serviceId = "jp.apliko.example";
  var storageKey = "username";

  apliko.storage.get(storageKey, function (data) {
    if (data.isSuccess) {
      $("#loginId").val(data.id);    // IDをフォームにセット
      apliko.securedata.get(serviceId, data.id, function (data) {
        if (data.isSuccess) {
          $("#loginPassword").val(data.password);    // パスワードをフォームにセット
        } else {
          // エラー時の処理
        }
      });
    } else {
      // エラー時の処理
    }
  });
}(jQuery));

このコードの問題点は以下ではないでしょうか。

  • 非同期処理が増えるほどネストが深くなる。
  • エラー時の処理が書きにくい。ユーザー名を取得するする処理が成功した場合の処理ブロック内に、パスワードを取得する処理が成功した場合・失敗した場合の処理が書いてある。

Promiseを利用して書いた場合

Promise.then()には、処理が成功した時に実行される関数と処理が失敗した時に実行される関数を指定します。改行の仕方、スペースの取り方に検討の余地はあるかもしれませんが、滝が流れるような感じで書けるのでとても分かりやすいのではないでしょうか。

(function ($) {
  var serviceId = "jp.apliko.example";
  var storageKey = "username";

  const promise = apliko.storage.get(storageKey);
  promise
    .then(
      function (id) {
        $("#loginId").val(id);    // IDをフォームにセット
        return apliko.securedata.get(serviceId, id)
      },
      function (message) {
        // エラー時の処理
      }
    )
    .then(
      function (password) {
        $("#loginPassword").val(password);    // パスワードをフォームにセット
      },
      function (message) {
        // エラー時の処理
      }
  );
}(jQuery));

コールバックとPromiseのどちらを採用するか?

サービスとして提供する上でコールバックパターンとPromiseパターンのどちらを採用するか少し迷いました。Promiseより前にjQueryでDeferredが話題になったこともありますが(私は「jQuery.Deferredって何 - Takazudo hamalog」をよく読みました。2011年の話です。)、コールバックが使われることが多かったのではないでしょうか。

ただ、コードの分かりやすさ、メンテナンスのしやすさ、そしてECMAScript 6などのトピックは多くの人が関心を寄せているであろうと推測したことをふまえ、最終的にPromiseパターンを採用しました。

今後も様々な観点から採用する技術を検討し、よりよいサービスを提供していきたいと考えています。

ネイティブ側とのやりとりを紹介

例えばユーザー名などのデータを取得するapliko.storage.get()を実行すると、apliko.promisesオブジェクトにresolverejectを保存し、Promiseを返します。ここでネイティブの処理も呼び出しています。

return new Promise((resolve, reject) => {
  // resolve, rejectをpromiseIdと共に保存
  apliko.promises[promiseId] = { resolve, reject };

  // ネイティブの処理を呼び出し
});

ネイティブ側(WKWebView)では指定された処理を実行し、apliko.utility.resolvePromise()を実行します。処理結果もここで渡します。

let script = "apliko.utility.resolvePromise('\(promiseId)', '\(message)');";
self.webView.evaluateJavaScript(script);

すると再び処理はJavaScript側に戻り、resolveもしくはrejectが実行されます。

apliko.utility.resolvePromise(promiseId, data, error) {
  if (error) {
    // エラーメッセージを伴って呼び出された場合はreject
    apliko.promises[promiseId].reject(error.message);
  } else {
    // エラーがない場合はresolve
    apliko.promises[promiseId].resolve(data);
  }

  // 保存した処理を削除
  delete apliko.promises[promiseId];
}

この一連の処理は「WKWebView. Return a value from native code to JavaScript – igoMobile」を参考にさせて頂きました。素晴らしいアイデアをご紹介頂きありがとうございます。

お問い合わせ Contact

制作のご依頼、ご相談などは下記のフォームからご連絡ください。