この記事では、Promiseの代わりにasync/awaitを使って、kintoneカスタマイズにおける非同期処理を書きやすくする方法を紹介します。
async/awaitとは
固定リンクがコピーされました
async/awaitは、APIにアクセスするような非同期処理の際も上から順に実行され、データを取得し変数に格納したり処理を待つことができるようにする記法です。
実際の使用例は後述します。
async/awaitはPromiseオブジェクトを利用しており、Promise処理を書きやすくしたものと考えて問題ありません。
Promiseの問題点
固定リンクがコピーされました
Promiseには次のようなデメリットがあります。
- 通常のコードに比べて理解が難しいこと
Promiseは非同期処理を扱うためのしくみですが、通常のコードに比べて理解が難しいことがあります。
then()
やcatch()
などのメソッドを使うことで、コードの流れが分かりにくくなることがあります。
- 連続する非同期処理では
then()
が増えて読みにくくなること
Promiseを使うと、非同期処理を連続して書く場合、then()
をつなげて書く必要があります。
そのため、ネストが深くなったり、読みづらくなる原因にもなります。
API通信などの非同期処理が行われる場合、JavaScriptの仕様上、同期的にレスポンスを待つことはできません。
このため、非同期の実行結果を扱う際にはPromiseなどのしくみが必要になります。
以下はPromiseを使って3回直列で他のレコードを取得する例です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
kintone.events.on(['app.record.create.submit', 'app.record.edit.submit'], (event) => {
return new kintone.Promise((resolve, reject) => {
return kintone.api('/k/v1/record.json', 'GET', params1).then((resp1) => { // レスポンス内容がresp1に入る。ちゃんと待ってから次の処理に移る
console.log(resp1);
return kintone.api('/k/v1/record.json', 'GET', params2);
}).then((resp2) => { // レスポンス内容がresp2に入る。ちゃんと待ってから次の処理に移る
console.log(resp2);
return kintone.api('/k/v1/record.json', 'GET', params3);
}).then((resp3) => { // レスポンス内容がresp3に入る。ちゃんと待ってから次の処理に移る
console.log(resp3);
resolve(event); // resolveでプロミスの処理が終了したことを伝える
});
});
});
|
async/awaitの利点
固定リンクがコピーされました
async/awaitを使うことで、次のようなメリットがあります。
- Promiseを使った非同期処理と比べて簡潔で直感的に書ける。
- 複数の非同期処理を順番に実行する場合でも、then()で何度もつなげる必要がない。
- Promiseをベースにしているため
.then()
や.all()
などのメソッドが使用できる。
ここでは、Promiseを使ったコードとasync/awaitを使ったコードを比較してみます。
このように書くだけで、通常の変数宣言のように、resp
変数にAPIから取得したResponseを格納できます。
ルールとしてawaitを使う場合は、その関数の先頭にasync
を付け、async関数だということを示す必要があります。
async関数はPromiseを返却します。
そのため、kintone側も結果を待つことができています。
async/awaitの使い方
固定リンクがコピーされました
このように、関数の頭にasync
を宣言し、待ちたい処理の箇所にawait
を宣言します。
async
をつけ忘れることも多いので気を付けてください。
実際のkintoneでの使い方の詳細は具体例で示します。
1
2
3
|
async () => {
await 非同期処理
}
|
MDNにも例がありますので必要に応じて参照してください。
mdn web docs:
async function
その他具体的な例も見ていきましょう。
比較のために通常のPromiseで書いたものと、async/awaitで書いたものを示します。
例1:レコード編集時に別レコードから取得したデータを使ってデフォルト値を設定する
固定リンクがコピーされました
Promiseを使った例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
kintone.events.on('app.record.edit.show', (event) => {
const params = {app: 1, id: 1}; // パラメータは任意のものを指定してください
// kintone.api()は第4引数を省略するとPromiseオブジェクトが返ってくるのでそのままreturnする。
// .then()でつないで処理を待つ
return kintone.api('/k/v1/record.json', 'GET', params)
.then((resp) => {
// フィールドコード文字列_1をAPIで取得したもので上書き
event.record.文字列_1.value = resp.record.文字列_1.value;
return event;
}).catch((e) => {
// パラメータが間違っているなどAPI実行時にエラーが発生した場合
alert(e.message);
return event;
});
});
|
async/awaitを使った例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
kintone.events.on('app.record.edit.show', async (event) => {
const params = {app: 1, id: 1}; // パラメータは任意のものを指定してください
try {
// kintone.api()は第4引数を省略するとPromiseオブジェクトが返ってくるのでそれに対してawaitする
// apiの返り値を変数に格納できる
const resp = await kintone.api('/k/v1/record.json', 'GET', params);
// フィールドコード文字列_1をAPIで取得したもので上書き
event.record.文字列_1.value = resp.record.文字列_1.value;
return event;
} catch (e) {
// パラメータが間違っているなどAPI実行時にエラーが発生した場合
alert(e.message);
return event;
}
});
|
エラー制御にはtry/catch
構文を使用します。
kintone.api()
からどのようなエラーが返却されるかは下記リファレンスを参考ください。
レスポンス - HTTPステータスコード
例2:レコード保存時に3つのレコードを取得し、合算する
固定リンクがコピーされました
Promiseを使った例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
kintone.events.on(['app.record.create.submit', 'app.record.edit.submit'], (event) => {
// kintone.api()は第4引数を省略するとPromiseオブジェクトが返ってくるのでそのままreturnする。
// .then()でつないで処理を待つ
// ※ サンプルとして今回は数珠つなぎにしていますがこのケースの場合はPromise.all()を使うことももちろん可能です。
return kintone.api('/k/v1/record.json', 'GET', params1)
.then((resp1) => {
event.record.合計.value += Number(resp1.record.小計.value);
// 2回目のAPI問い合わせ
return kintone.api('/k/v1/record.json', 'GET', params2);
// .then()でつないで処理を待つ
}).then((resp2) => {
event.record.合計.value += Number(resp2.record.小計.value);
// 3回目のAPI問い合わせ
return kintone.api('/k/v1/record.json', 'GET', params3);
// .then()でつないで処理を待つ
}).then((resp3) => {
event.record.合計.value += Number(resp3.record.小計.value);
return event;
}).catch((e) => {
// パラメータが間違っているなどAPI実行時にエラーが発生した場合
alert(e.message);
return event;
});
});
|
async/awaitを使った例
このように特に数珠つなぎで書く場合、非常に見通しがよくなりますね。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
kintone.events.on(['app.record.create.submit', 'app.record.edit.submit'], async (event) => { // asyncをつける
try {
// ↓待ちたい処理にawaitをつける
const resp1 = await kintone.api('/k/v1/record.json', 'GET', params1); // レスポンス内容がresp1に入る。ちゃんと待ってから次の処理に移る
const resp2 = await kintone.api('/k/v1/record.json', 'GET', params2); // レスポンス内容がresp2に入る。ちゃんと待ってから次の処理に移る
const resp3 = await kintone.api('/k/v1/record.json', 'GET', params3); // レスポンス内容がresp3に入る。ちゃんと待ってから次の処理に移る
// ここにくるまでに全てのデータの取得を完了しているので計算できる
// resp1 - 3までの合計をたして「合計フィールド」にセット
event.record.合計.value = Number(resp1.record.小計.value) + Number(resp2.record.小計.value) + Number(resp3.record.小計.value);
return event;
} catch (e) {
// パラメータが間違っているなどAPI実行時にエラーが発生した場合
alert(e.message);
return event;
}
});
|
Promiseを考慮する(Promiseは不要ということではない)
固定リンクがコピーされました
async/awaitを使えば一切Promiseについて知らなくてもよいかというとそういうことではなく、前述のとおり非同期処理を書きやすくなっただけですので、Promiseの概念自体は知っている必要があります。
Promiseも使えるとベターです。
1
2
3
4
5
|
// async/awaitと従来のPromiseの書き方を混ぜた例
const resp2 = await kintone.api('/k/v1/record.json', 'GET', {app: 1, id: 1}).then((resp1) => {
return kintone.api('/k/v1/record.json', 'GET', {/* resp1によってparamを変える */});
});
|
上記のように書くなら、async/awaitで統一したほうがよいです。
1
2
|
const resp1 = await kintone.api('/k/v1/record.json', 'GET', params1);
const resp2 = await kintone.api('/k/v1/record.json', 'GET', {/* resp1によってparamを変える */});
|
Promiseとasync/awaitには次のような関係性があります。
- awaitを使ってPromiseの処理の終了を待つことができる。
すでに例で示してあるとおり、kintone.api()
をawaitで待つことができます。
それはkintone.api()
はPromiseオブジェクトを返却しているためです。
- async関数はPromiseを返却する。
async関数はPromiseオブジェクトを返却します。
そのため上記のようにthenとawaitを両方使い合わせることができます。
ただし、混乱の元になりますので極端に書き方を混在させるようなことはしないほうがよいでしょう。
async/awaitは、Promiseを使った非同期処理をより簡潔かつ直感的に記述するための構文です。
これにより、非同期処理のコードは可読性が高くなり、メンテナンスもしやすくなります。
他アプリのデータ取得など非同期処理が必要な場面で活用していきましょう。