はじめに
kintoneをJavaScriptでカスタマイズしていると、時々意図していない動きに遭遇します。
その度に、「ああ、これは非同期処理なんだからPromiseを使って順番に処理する必要があるんだっけ」と、その度に"Promise"の書き方を確認して、デバッグしながら「何とか動いたので良しとしよう」とやってるのですが、もやもやが溜まるばかり。
今回、Promiseの基礎的な部分を自分なりに整理しておくことにしました。
「基礎であるが初歩では無い」あたりを埋めてみたいと思います。
対象者
- JavaScript以外のプログラムの経験が結構ある
- JavaScriptのプログラムをまあまあ書ける
- kintoneでJSカスタマイズができる
kintone Promise とは?
まず、kintoneにPromiseが実装された背景などはこちらのリンクをご一読ください。
kintone API で Promise を使ってみよう!
次の流れで、kintone.Promiseとは
という記事がありますが、こちらを読んでも
結論:kintone.Promiseとは
な~んか処理の順番が上手くいかない時や、
な~ぜか上手く処理が反映されない時に使うと解決できるかもしれないもの。
とあって、結局もやっとしたものが残ります。
参考のコードも売上管理・在庫管理などで難しい。
Promiseは売上管理や在庫管理の処理をする際に使うものか?と思ってしまいますが、意外と単純な処理でもPromiseを使わざるを得ない場合はあります(;-)
JavaScript Promise
kintone Promiseの前に JavaScript Promise の理解が必要です。
-
MDN Promiseを使う
-
(Promise)[https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise]
Promiseとは何かの定義は上記Promiseの方が分かりやすいかと思います。
解説から引用します
Promise インターフェイスは作成時点では分からなくてもよい値へのプロキシです。
Promise を用いることで、非同期アクションの成功や失敗に対するハンドラーを関連付けることができます。
これにより、非同期メソッドは、最終的な値を返すのではなく、未来のある時点で値を持つ Promise を返すことで、同期メソッドと同じように値を返すことができるようになります。
つまり、ブラウザのイベント(例えばマウスをクリックしたとか)などの際の動作で発火するイベントリスナーの登録と同じように、Promiseを使って非同期処理の際の成功と失敗に対するハンドラーを事前に登録できると言うイメージです。
それではPromseの使い方を見ていきます。
Promise() コンストラクター
Promiseを返す関数を作る
Promiseコンストラクターで関数をラップすることで、関数がPromiseを返すことができるようになります。
こんな感じ
Promiseを返す関数 promise1
const promise1 = (value) => {
return new Promise(resolve => {
resolve(value.toUpperCase());
})
};
console.log(promise1()); // Promise {}
作った関数をconsole.logすると、Promise と帰ってくるので、確かにPromiseを返す関数になっているようです。
Promiseを返す関数を利用する
で、作った関数をどう使うかですが、これは下記のようにして使います。
promise1('Hello').then(result => {console.log(result)});
冗長のようですが Promise というのはこういうものと思ってください。
関数が1つだけだと説明にならないのでもう一つ追加します。
Promiseを返す関数 promise2
const promise2 = (value) => {
return new Promise(resolve => {
resolve(value.toLowerCase());
})
};
Promiseを繋げる
それから、promise1 と promise2 を順番に処理させたい時は下記のように繋ぎます。
promise1('Hello')
.then(result1 => { // result1 に最初のPromise関数の結果が返ってくる
console.log(result1); // 戻り値を使って何か処理する
return promise2(result1) // ここで次のPromiseをリターンする
})
.then(result2 => { // 次のPromise関数の結果が返ってくる
console.log(result2); // 戻り値を使って何か処理する
})
間に then() 関数を入れて繋げるだけです。(いわゆるPromiseチェーン)
簡単ですね。
- then() メソッド https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
then関数の中にコールバック関数を書くことでPromiseを返す関数を処理の順番に繋げることができます。
エラー処理
エラー処理はPromiseを返す関数の中で処理しても良いですし、作った関数を使う側で下記のようにしても良いです。
promise1('Hello')
.then(result1 => {
console.log(result1);
return promise2(result1)
})
.then(result2 => {
console.log(result1); // ここでエラーが発生
console.log(result2);
})
.catch(error => { // ここでエラーをキャッチする
console.log(error); // :ReferenceError: result1 is not defined
})
kintone Promise 再び
まずサンプルコードです。
サンプルコード
(function() {
'use strict';
kintone.events.on('app.record.detail.show', (event) => {
return new kintone.Promise((resolve) => {
resolve(kintone.app.getId());
})
.then(result => {
const params = {'app': result};
return kintone.api(kintone.api.url('/k/v1/app/form/fields', true), 'GET', params);
})
.then(result => {
const fields = getFields(result, 'GROUP');
fields.forEach(field => {kintone.app.record.setGroupFieldOpen(field.code, false)});
// return event; // 処理によってはeventをリターンしなくてもOK
})
.catch(error => {
console.error(error);
});
});
function getFields (fields, fieldType) {
let result = [];
try {
const values = Object.values(fields.properties);
if (fieldType) {
values.map((field) => {
if (field.type === fieldType) {
result.push(field);
}
});
} else {
result = values;
}
return result;
} catch (error) {
return error;
}
}
})();
kintoneのエラーを判別して処理する場合
// 最初の処理
// fetchRecordsはkintoneに何か処理をする関数
fetchRecords(URL, option)
.then(result => {
if (result.getResponseCode() !== 200) { // ステータスコードが200以外の時
throw new Error(result.getContentText()); // エラーをスローする
}
return result;
})
.then(result => {
// 次の処理
console.log(result.getContentText());
})
.catch(error => {
// スローされたエラーキャッチして処理する
console.error(error);
});
}
kintone.Promise CheatSheet
kintoneのPromiseの書き方については、デベロッパーサイトにもいくつかの書き方がありどれにすれば良いのか迷いますが、
まずは基本の書き方1つで行きましょう。
テンプレートを載せておきます。
kintone.events.on('<kintoneイベント>', (event) => {
return new kintone.Promise((resolve) => {
// 何か処理
resolve(<次に渡したい結果など>); // Promiseチェーンで繋げたい結果はresolve()で返却する
})
.then(result => {
// 何か処理
// REST APIでレコードを取得など。 下記の書き方をすれば、処理結果がPromiseで返される。
return kintone.api(kintone.api.url('<REST API エンドポイント>', true), '<GET/POST>', <パラメータ>);
})
.then(result => {
// 何か処理
return event; // 結果をレコードに反映するなどの処理に応じて、最後にeventをリターンする。
})
.catch(error => {
console.error(error);
});
});
上記のテンプレートを使ってもらえれば、下記のようにPromiseがネストしていくような書き方にならずに済むかと。。。
.then(result => {
return kintone.api(kintone.api.url('<REST API エンドポイント>', true), '<GET/POST>', <パラメータ>)
.then(result => {
return kintone.api(kintone.api.url('<REST API エンドポイント>', true), '<GET/POST>', <パラメータ>)
.then(result => {
return kintone.api(kintone.api.url('<REST API エンドポイント>', true), '<GET/POST>', <パラメータ>)
.then(result => {