弊社では目下、Movable Type / PowerCMSで運用中のウェブサイトを10分でアプリ化する「Apliko(アプリコ)」の新テーマ開発を行っています。
開発の中でJavaScript(HTMLで作成した画面)からネイティブの機能を呼ぶ必要があり独自のJavaScriptライブラリを開発しました。その際、非同期処理を分かりやすく記述できる「Promise」を利用しましたのでご紹介したいと思います。
処理の流れ
apliko.storage.get()
でネイティブに保存しているユーザー名を取得し、フォームのID欄にセットする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
オブジェクトにresolve
とreject
を保存し、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」を参考にさせて頂きました。素晴らしいアイデアをご紹介頂きありがとうございます。