kintone カスタマイズで非同期処理をする

目次

はじめに

前回は、 kintone REST API リクエストを送信する APIkintone.api())のコールバック関数を使う方法で、kintone REST API を実行する方法を説明しました。

この記事では、kintone.api() を使って kintone REST API を実行する次の 3 つの方法を、詳しく説明します。

方法1:コールバックを使う

kintone.api() を使って kintone REST API を実行するときは、第 4 引数に成功したときのコールバック関数を指定します。
これが API ドキュメント内で「コールバックを使う方法」として紹介されている方法です。
前回の kintone REST API の記事 で学んだ方法ですね。

1
kintone.api(APIのパス, メソッド, リクエストパラメータ, 成功したときのコールバック関数, 失敗したときのコールバック関数)

例として、あるアプリのレコード追加画面を表示したときに kintone REST API を実行し、商品アプリから取得したレコードの内容をコンソールに表示してみましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
(() => {
  'use strict';
  kintone.events.on('app.record.create.show', (event) => {
    const successCallback = (resp) => {
      const itemRecord = resp.record;
      console.log(itemRecord); // 取得したレコードの内容をコンソールに出力する
    };
    const failureCallback = (err) => {
      console.error(err);
    };
    // 商品アプリからレコードを取得する
    // 第4引数に成功したときのコールバック関数、第5引数に失敗したときのコールバック関数を渡す
    kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: 1, id: 1}, successCallback, failureCallback);
    return event;
  });
})();

successCallbackfailureCallback のように名前を付けた関数を引数に指定せず、そのまま関数を書くこともあります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(() => {
  'use strict';
  kintone.events.on('app.record.create.show', (event) => {
    // 商品アプリからレコードを取得する
    kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: 1, id: 1}, (resp) => {
      const itemRecord = resp.record;
      console.log(itemRecord); // 取得したレコードの内容をコンソールに出力する
    }, (err) => {
      console.error(err);
    });
    return event;
  });
})();

コールバックを使う方法は、kintone.api() を使うときの基本的な書き方ではありますが、注意することが 2 つあります。

  • 非同期処理の完了を待たずに続きの処理が実行される。
  • 非同期処理を何度も行うような場合に、コールバック地獄が発生しやすい。

どういうことか、詳しく確認していきましょう。

非同期処理の完了を待たずに続きの処理が実行される

レコードを保存するときに別のアプリからレコードを取得して、その結果をフィールドにセットしたいケースがよくあります。
たとえば、見積アプリのレコードを保存するとき、商品アプリに登録されているレコードの「定価」の値を「金額」フィールドへセットして保存したい、といった場面です。
このとき、コールバックを使う方法で kintone.api() を実行すると、「金額」フィールドが空のまま登録されてしまいます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 見積もりアプリに適用するカスタマイズ
(() => {
  'use strict';
  kintone.events.on('app.record.create.submit', (event) => {
    // 商品アプリからレコードを取得する
    kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: 1, id: 1}, (resp) => {
      const itemRecord = resp.record;
      event.record['金額'].value = itemRecord['定価'].value;
    }, (err) => {
      console.error(err);
    });
    return event;
  });
})();

上記のコードは以下の順番で処理が進むため、「金額」フィールドが空のレコードが登録されます。

  1. kintone.api() で、レコードを取得する API を実行する。
  2. return event で、イベントオブジェクトを返却する。
  3. レコードを登録する。
  4. API のリクエスト結果を受け取り、 イベントオブジェクトに代入する。

非同期処理を何度も行うような場合に、コールバック地獄が発生しやすい

コールバック地獄とは、コールバック関数の中にコールバック関数があるような入れ子状態となり、コードが読みづらくなってしまう状況のことです。
たとえば、商品アプリから 3 つのレコードを順に取得する場合を考えてみましょう。

 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
(() => {
  'use strict';
  kintone.events.on('app.record.create.show', (event) => {
    const itemAppId = 1;
    kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: itemAppId, id: 1}, (resp1) => {
      // レコード ID が 1 のレコード
      console.log(resp1.record['商品名'].value);
      kintone.api(kintone.api.url('/k/v1/records.json', true), 'GET', {app: itemAppId, id: 2}, (resp2) => {
      // レコード ID が 2 のレコード
        console.log(resp2.record['商品名'].value);
        kintone.api(kintone.api.url('/k/v1/records.json', true), 'GET', {app: itemAppId, id: 3}, (resp3) => {
          // レコード ID が 3 のレコード
          console.log(resp3.record['商品名'].value);
        }, (err3) => {
          console.error(err3);
        });
      }, (err2) => {
        console.error(err2);
      });
    }, (err1) => {
      console.error(err1);
    });
    return event;
  });
})();

コールバック関数の入れ子が深くなっていて、括弧の対応関係がわかりにくくなっています。
kintone.api() の実行が増えれば増えるほど、この入れ子はどんどん深くなってしまいます。

方法2:Promise を使う

コールバックを使う方法で発生するこれら 2 つの問題を解決するには、Promise を使います。
Promise を使うと、非同期処理を同期的に(順番に)処理でき、コールバック地獄からも解放されます。

非同期処理の完了を待たずに続きの処理が実行される問題を解決する

まずは、Promise を使って、非同期処理の完了を待たずに続きの処理が実行される問題を解決してみましょう。
非同期処理の完了を待たずに続きの処理が実行される を Promise を使って書き直すと、次のようなコードになります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
(() => {
  'use strict';
  kintone.events.on('app.record.create.submit', (event) => {
    // 商品アプリからレコードを取得する
    return kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: 1, id: 1}).then((resp) => {
      const itemRecord = resp.record;
      // 商品アプリから取得した「定価」を見積アプリの「金額」に代入する
      event.record['金額'].value = Number(itemRecord['定価'].value);
      return event;
    }).catch((err) => {
      console.error(err);
    });
  });
})();

ポイントは、5 行目の return kintone.api(...) です。
kintone.api() は、コールバック関数を省略すると、Promise オブジェクトが戻り値になります

1
kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: 1, id: 1}); // Promise オブジェクトが返される

kintone のイベントは、Promise オブジェクトが return されると非同期処理の実行を待つしくみを持っています *1
コードの例の場合は、商品アプリからレコードを取得する処理を待ってから、kintone.events.on('app.record.create.submit', ...) を完了します。
非同期処理が成功したときの結果は、then() メソッド内で受け取ることができます。
そのため、商品アプリから取得した「定価」を見積アプリの「金額」に代入する処理は、then() のコールバック関数内に記述します(6〜9 行目)。

*1 一部のイベントでは、非対応です。 ^

コールバック地獄を解決する

次に、Promise を使って、コールバック地獄を解決してみましょう。
コールバック関数を使った例 を Promise を使って書き直します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
(() => {
  'use strict';
  kintone.events.on('app.record.create.show', (event) => {
    const itemAppId = 1;
    // 商品アプリからレコードを取得する
    return kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: itemAppId, id: 1}).then((resp1) => {
      // レコード ID が 1 のレコード
      console.log(resp1.record['商品名'].value);
      return kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: itemAppId, id: 2});
    }).then((resp2) => {
      // レコード ID が 2 のレコード
      console.log(resp2.record['商品名'].value);
      return kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: itemAppId, id: 3});
    }).then((resp3) => {
      // レコード ID が 3 のレコード
      console.log(resp3.record['商品名'].value);
      return event;
    });
  });
})();

先ほどの コールバック関数を使った例 と比べると、入れ子が浅くなり、コードの見通しが良くなりました。
ポイントは、6 行目、 9 行目と 13 行目で、return kintone.api() しているところです。
Promise オブジェクトを return しているので、 kintone.api() の実行の完了を待ち、それぞれ後続の then() 内で、その前に取得したレコードのオブジェクトを利用できます。

方法3:async/await を使う

非同期処理の記事 で、async/await を使うと、よりシンプルに非同期処理を書くことができると紹介しました。
kintone.api() でも、async/await を使ってみましょう。

次のコードは、 Promise で非同期処理の完了を待たずに続きの処理が実行される問題を解決する で紹介したコードを、async/await で書き直したものです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
(() => {
  'use strict';
  kintone.events.on('app.record.create.submit', async (event) => {
    try {
      const itemResp = await kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: 1, id: 1});
      const itemRecord = itemResp.record;
      // 商品アプリから取得した「定価」を見積アプリの「金額」に代入する
      event.record['金額'].value = Number(itemRecord['定価'].value);
    } catch (err) {
      console.error(err);
    }
    return event;
  });
})();

async/await を使うときのポイントは、次の 2 つでしたね。

  • 実行する非同期関数の先頭に、await をつける。
  • await を付けた行は、async 関数で囲む。

そのため、さきほどの例では、次のように修正しています。

  • await kintone.api() にする(5 行目)
  • kintone.events.on のコールバック関数に async をつける(3 行目)

Promise でコールバック地獄を解決する の例も、async/await を使って書き直してみましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
(() => {
  'use strict';
  kintone.events.on('app.record.create.show', async (event) => {
    try {
      const itemAppId = 1;
      // 商品アプリからレコードを取得する
      const item1Resp = await kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: itemAppId, id: 1});
      console.log(item1Resp.record['商品名'].value);
      const item2Resp = await kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: itemAppId, id: 2});
      console.log(item2Resp.record['商品名'].value);
      const item3Resp = await kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: itemAppId, id: 3});
      console.log(item3Resp.record['商品名'].value);
    } catch (err) {
      console.error(err);
    }
    return event;
  });
})();

then() でつないでいた処理がなくなり、さらに簡潔になりました!

おわりに

kintone で非同期処理を同期的に行う方法を紹介しました。
難しい内容ですが、kintone カスタマイズを行う上では理解が必須の知識です。
次回は デバッグ について学習します。コードが動かないときの対処法を学びましょう。