経験豊かなSalesforceの開発者のみなさんは、止むに止まれぬ理由によりLightningコンポーネントの開発をする羽目になったとしても、不自由フレームワークから巧みに逃れ、巷で定評のあるオープンなフレームワーク/ライブラリを使用して生産性をガンガンに上げていらっしゃることでしょう。
さて、なぜそのような自由が謳歌できていたのかというと、Lightningフレームワークにはフレームワークの管理コンテキストの外からでもLightningの機能(サーバ呼び出しやイベントなど)を呼び出すことができる機能があったからです。これは$A.run
という関数なのですが、Winter'16 から$A.runが廃止され代わりに$A.getCallback
を使うようになりました。
しかしながら、まだ$A.getCallback
の使い方はマニュアルにちょっとあるだけなので、どのようにしたらよいかわからない方も多いでしょう。
$A.getCallback
は、単に$A.run
の置き換えというわけではありません。ちょっと奇妙な制約が設けられています。これはLightningフレームワークのAura Loopというひどい独特な設計が所以なのですが、そのことが分かるまで開発者はかなり苦労することになるでしょう。
例えば、5秒タイマーの後にLightningコンポーネントの属性をセットしたいとき、$A.run
を利用した場合はこんな感じにしていました。
window.setTimeout(function() {
$A.run(function() {
cmp.set("v.visible", true);
});
}, 5000);
Lightningフレームワークでは、すべてのコンポーネント操作はAura Loopと呼ばれるコンテキスト内で行われる必要があるという傲慢な斬新な制約があるので、このような事を行う必要があるのです。おそらく$A.run
の動きとしては、引数で受け取った処理を実行キューに入れて、次のAura Loopが回ってきた時に実行するように指定する、といったイメージが近いかもしれません。
一方、$A.getCallback
は、関数を引数として受け取り、新たな関数を返す関数になっています。下はマニュアルにもあるコード例です。
window.setTimeout(
$A.getCallback(function() {
if (cmp.isValid()) {
cmp.set("v.visible", true);
}
}),
5000
);
コンポーネントへのアクセスを実行する前にcmp.isValid()
を呼び出して状態チェックが追加されていますが、そこは本題ではないのでとりあえずおいておきます。
マニュアル等には明示的に書かれていないのですが、もっとも重要な点は、$A.getCallback
を呼び出すときは、必ずAura Loopにはいっている必要があるということです。$A.run
はどのコンテキストから呼んでも大丈夫でしたが、$A.getCallback
はそうはいきません。
つまり、単に$A.run
を置き換えるような以下のコードでは駄目ということです。実際実行するとエラーが発生します。
window.setTimeout(function() {
// この処理が実行されている時点ですでにAura Loopの外側になっているため、$A.getCallbackは利用できない
$A.getCallback(function() {
if (cmp.isValid()) {
cmp.set("v.visible", true);
}
})();
}, 5000);
もちろん、コンポーネントにアクセスするのですからコールバック関数を生成するときはすでにAuraのコンテキストに入っている場合がほとんどかと思いますが、多段の非同期呼び出しコールバックが介在しているときにはすべての段でコールバック関数をWrapしていかなければいけません。
getFooAsync($A.getCallback(function(foo) {
getBarAsync(foo, $A.getCallback(function(bar) {
getBazAsync(bar, $A.getCallback(function(baz) {
if (cmp.isValid()) {
cmp.set("v.foo", foo);
cmp.set("v.bar", bar);
cmp.set("v.baz", baz);
}
}));
}));
}));
これはちょっと大変ですし、もう少し複雑になってくるとちゃんとすべての関数コールバックがトラックできているのかどうか不安になってくるでしょう。
しかたがないので、自作で$A.run
相当を作ってみます。以下のmyAuraRun
関数は、$A.run
と同様に、関数を引数として受け取り、処理をAura Loop内で実行します。
// 最初の定義はAura Loop内であることが前提
window.myAuraRun = $A.getCallback(function(fn) {
// getCallbackでwrapされた処理はAura Loop内で実行されるため、fnの実行もAura Loop内になる
if (cmp.isValid()) { fn(); }
});
// 以下、Aura Loopの外から必要になった時の呼び出し方
setTimeout(function() {
window.myAuraRun(function() {
cmp.set("v.visible", true);
});
}, 5000);
上記myAuraRun
の定義自体は最初にAura Loop内でやっておく必要はありますが、定義されたmyAuraRun
はAura Loop以外のどこから呼び出しても大丈夫になりました。めでたしめでたし?
というか、たとえ$A.getCallback
の仕組みが内部的に必要になったのだとしても、上記の方法でちゃんと代替できるのに、なんでわざわざ$A.run
を廃止したんでしょうかねえ。