前提
kintoneのJSカスタマイズで鬼門になるのは、非同期処理だと思っています。というか、JavaScript自体にとっても非同期処理のややこしさはプログラミングの鬼門でもあるはずですが、どちらにせよ、kintoneから入門したJSerやJSに不慣れなプログラマにとっては厄介な問題です。
「非同期処理」とはkintoneのJSカスタマイズでは複数のアプリからデータを取得して計算させたり、ある処理が完了してから保存をさせたり、ということですね。下記がよくある間違いです。
(function() {
"use strict";
// 2件のデータを取得し合計値を表示する
// params1-2は省略します。
kintone.events.on(["app.record.index.show"], function(event) {
var sum = 0;
// API呼び出し1回目
kintone.api('/k/v1/record', 'GET', params1, function(resp1){
sum += resp1.record.小計.value;
});
// API呼び出し2回目
kintone.api('/k/v1/record', 'GET', params2, function(resp2){
sum += resp2.record.小計.value;
});
// APIの処理が終わる前にここに到達してしまう
console.log(sum);
});
});
複数のデータをAPIで取得して、合計値を表示したいのですが上記だとうまくいきません。APIでデータを取得するまえに console.log(sum);
に到達してしまうからですね。
JavaScriptにおける非同期の扱い
参考程度の小話です。JavaScriptはシングルスレッドで動いていて、もともと並行処理はできません。そのため、待ちが発生する処理があると、通り過ぎて次の処理を実行しようとしてしまいます。なので非同期的な動きになってしまいます。待ちが発生するのは、kintoneのJSカスタマイズでは主にkintone APIなどを呼ぶ通信処理で発生します。通信処理はネットワークの環境によってレスポンスの早さが左右されますので、JavaScriptとしては待ってられないのでさっさと通り過ぎてしまうわけです。ちなみに、無理やり待つこともできますが、その間ブラウザがハングアップしてしまいます。
非同期を扱うための「コールバック関数」
Aの処理が終わったらBの処理を実行する、というのはベーシックにはコールバック関数を用いることで解決します。
前述の失敗例も event.record.合計.value += resp1.record.小計.value;
という部分はコールバック関数内で実行していますが、それ以外もコールバック関数を使って処理させるとうまくいきます。
(function() {
"use strict";
// 2件のデータを取得し合計値を表示を想定
// params1-2は省略します。
var sum = 0;
kintone.events.on(["app.record.index.show"], function(event) {
// API呼び出し1回目
kintone.api('/k/v1/record', 'GET', params1, function(resp1){
// API呼び出し2回目
kintone.api('/k/v1/record', 'GET', params2, function(resp2){
event.record.合計.value += resp1.record.小計.value;
event.record.合計.value += resp2.record.小計.value;
// APIの処理が終わる前にここに到達してしまう
console.log(sum);
});
});
return event;
});
});
コールバック関数の問題点
ただし、問題になってくるのが、ネスト(階層)が深くなってきたときです。「Aの処理が終わったらBの処理を実行する」程度では苦にはなりませんが、これが「A→B→C→D→E...」という風にいくつも重なると人間には理解しづらくなってきます。上記の2つのネストでも結構分かりづらいですね。
また、コールバック関数にいれただけでは、kintoneでは保存時前処理を待ってはくれません。上記のように sum
変数を保存時にレコードに保存したい場合は、下記のPromiseが必要になってきます。
非同期を扱うための「Promise」
コールバックの問題点を解消するために、JavaScriptにはPromiseというものが実装されました。ネストが深くなっても下記のように書けます。
また、kintoneAPIもPromiseに対応していて、データ保存前の処理のPromiseを待って保存してくれたり、Promise非対応のInternetExplorerでもPromiseができるように図らってくれています。
(function() {
"use strict";
// 4件のデータを取得し合計値を求めることを想定
// params1-4は省略します。
// kintoneはPromiseを返すことで保存前処理など、処理をまってくれる
kintone.events.on(["app.record.create.submit", "app.record.edit.submit"], function(event) {
return new kintone.Promise(function(resolve, reject) {
return kintone.api('/k/v1/record', 'GET', params1);
}).then(function(resp1) { // レスポンス内容がresp1に入る。ちゃんと待ってから次の処理に移る
return kintone.api('/k/v1/record', 'GET', params2);
}).then(function(resp2) { // レスポンス内容がresp2に入る。ちゃんと待ってから次の処理に移る
return kintone.api('/k/v1/record', 'GET', params3);
}).then(function(resp3) { // レスポンス内容がresp3に入る。ちゃんと待ってから次の処理に移る
return kintone.api('/k/v1/record', 'GET', params4);
}).then(function(resp4) { // レスポンス内容がresp4に入る。ちゃんと待ってから次の処理に移る
// ここにくるまでに全てのデータの取得を完了しているので計算できる
// resp1 - 4 までの合計をたして「合計フィールド」にセット
event.record.合計.value = resp1.record.小計.value + resp2.record.小計.value + resp3.record.小計.value + resp4.record.小計.value;
resolve(event); // resolveでプロミスの処理が終了したことを伝える
});
});
})();
Promiseの問題点
多少扱いやすくはなったものの、then
やresolve
,reject
など、慣れてないと混乱してきて結局思うようにうごかなかったりします。
async/awaitがあります
おしなべて、元をたどれば「普通に書きたい(同期処理的に書きたい)」ということなのですが。それを実現してくれる仕組みが一部ブラウザでは実装されはじめています。
async/awaitで書けば下記のようにわかりやすくなります。
(function() {
"use strict";
// 4件のデータを取得し合計値を求めることを想定
// params1-4は省略します。
kintone.events.on(["app.record.create.submit", "app.record.edit.submit"], async function(event) { // async をつける
// ↓待ちたい処理にawaitをつける
var resp1 = await kintone.api('/k/v1/record', 'GET', params1); // レスポンス内容がresp1に入る。ちゃんと待ってから次の処理に移る
var resp2 = await kintone.api('/k/v1/record', 'GET', params2); // レスポンス内容がresp2に入る。ちゃんと待ってから次の処理に移る
var resp3 = await kintone.api('/k/v1/record', 'GET', params3); // レスポンス内容がresp3に入る。ちゃんと待ってから次の処理に移る
var resp4 = await kintone.api('/k/v1/record', 'GET', params4); // レスポンス内容がresp4に入る。ちゃんと待ってから次の処理に移る
// ここにくるまでに全てのデータの取得を完了しているので計算できる
// resp1 - 4 までの合計をたして「合計フィールド」にセット
event.record.合計.value = resp1.record.小計.value + resp2.record.小計.value + resp3.record.小計.value + resp4.record.小計.value;
return event;
});
})();
async/awaitを使うには、
- 関数に
async
をつける - 待ちたい処理に
await
をつける
だけです。
ちなみに、中身はPromiseオブジェクトなので、ちゃんとkintoneも保存時の処理を待ってくれます。
注意点
互換性
ただし、IEは残念ながらサポートしていません。この機能を使う場合は対象ブラウザをChromeなどに限定をすることが必要になってきます。
各ブラウザの対応状況はこちらのリファレンスを確認ください。
webpackというビルドツールを使えばIEでも動かす方法もありますが、そういうツールに慣れてないと大変かもしれません。
エラー制御
非同期処理においてはエラー制御がちょっと面倒ですので、慣れてきたら下記あたりも参照するといいと思います。
https://qiita.com/gaogao_9/items/40babdf63c73a491acbb