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カスタマイズを行う上では理解が必須の知識です。
次回は デバッグをしてみようについて学習します。
コードが動かないときの対処法を学びましょう。