Promiseのかわりにasync/awaitを使ってみよう

目次

はじめに

この記事では、Promiseの新しい記述方法のひとつ、async/awaitを使って、kintoneカスタマイズにおける非同期処理を書きやすくする方法を紹介します。

async/awaitとは

APIにアクセスするときも、普通のコードを書くように、上から下に順次で同期的に実行され、データをとって変数にいれたり処理を待ているよう、async/awaitはそれを実現できる新しい記法です。
実際のサンプルは後述します。

async/awaitはPromiseオブジェクトを利用しており、Promise処理が書きやすくなったものと理解して差し支えありません。

Promiseの問題点

Promiseは次のようなデメリットがあります。

  • Promise以外の通常のコードと比較すると難しい。
    Promiseに慣れていないと、通常のコードを書くのと比べるとかなり難しく感じてしまいます。
    理由は、通常のコードは普通に書くだけで上から下に処理が進むのに、Promiseの場合はresolveやthenなどを駆使してうまく数珠つなぎにして書かないといけないことだと思います。
  • 連続した非同期処理を書く場合はthen()でつないだりと、さらに複雑になる。

上から下に順次で同期的にコードが実行されないのは、APIに通信する際など非同期処理が行われる場合です。
JavaScriptの制約上、レスポンスが返ってくるまでそのまま待つということができません。
やろうとするとブラウザー自体をフリーズさせてしまうためです。

下記は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', 'GET', params1).then((resp1) => { // レスポンス内容がresp1に入る。ちゃんと待ってから次の処理に移る
      console.log(resp1);
      return kintone.api('/k/v1/record', 'GET', params2);
    }).then((resp2) => { // レスポンス内容がresp2に入る。ちゃんと待ってから次の処理に移る
      console.log(resp2);
      return kintone.api('/k/v1/record', 'GET', params3);
    }).then((resp3) => { // レスポンス内容がresp3に入る。ちゃんと待ってから次の処理に移る
      console.log(resp3);
      resolve(event); // resolveでプロミスの処理が終了したことを伝える
    });
  });
});

async/awaitの利点

async/awaitを使うと、次のようなメリットがあります。

  • 非同期処理にPromiseを使うのを比べて簡潔で直感的にかけるようになる。
  • 連続した非同期処理を書く場合でも、then()でつなげて書く必要がない。
  • Promise利用時と同じくPromiseオブジェクトを扱うことになるため.then().all()などのPromise関数も合わせて使える。

通常のPromiseを使ったものと比較してみます。

  • 通常のPromiseでレコード詳細画面を開いたときに他のレコードを取得する。

    1
    2
    3
    4
    5
    
    kintone.events.on('app.record.detail.show', () => {
      return kintone.api('/k/v1/record', 'GET', {app: 1, id: 1}).then((resp) => {
        console.log(resp); // 取得内容表示
      });
    });
  • async/awaitを使ってレコード詳細画面を開いたときに他のレコードを取得する。

    1
    2
    3
    4
    
    kintone.events.on('app.record.detail.show', async () => {
      const resp = await kintone.api('/k/v1/record', 'GET', {app: 1, id: 1});
      console.log(resp); // 取得内容表示
    });

このように書くだけで、通常の変数宣言のように、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 (External link)

具体例

その他具体的な例も見ていきましょう。
比較のために通常の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()は第3引数を省略するとPromiseオブジェクトが返ってくるのでそのままreturnする。
  // .then()でつないで処理を待つ
  return kintone.api('/k/v1/record', '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()は第3引数を省略するとPromiseオブジェクトが返ってくるのでそれに対してawaitする
    // apiの返り値を変数に格納できる
    const resp = await kintone.api('/k/v1/record', '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()は第3引数を省略するとPromiseオブジェクトが返ってくるのでそのままreturnする。
  // .then()でつないで処理を待つ
  // ※ サンプルとして今回は数珠つなぎにしていますがこのケースの場合はPromise.all()を使うことももちろん可能です。
  return kintone.api('/k/v1/record', 'GET', params1)
    .then((resp1) => {
      event.record.合計.value += Number(resp1.record.小計.value);
      // 2回めのAPI問い合わせ
      return kintone.api('/k/v1/record', 'GET', params2);
      // .then()でつないで処理を待つ
    }).then((resp2) => {
      event.record.合計.value += Number(resp2.record.小計.value);
      // 3回目のAPI問い合わせ
      return kintone.api('/k/v1/record', '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', 'GET', params1); // レスポンス内容がresp1に入る。ちゃんと待ってから次の処理に移る
    const resp2 = await kintone.api('/k/v1/record', 'GET', params2); // レスポンス内容がresp2に入る。ちゃんと待ってから次の処理に移る
    const resp3 = await kintone.api('/k/v1/record', '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', 'GET', {app: 1, id: 1}).then((resp1) => {
  return kintone.api('/k/v1/record', 'GET', {/* resp1によってparamを変える */});
});

上記のように書くなら、async/awaitで統一したほうがいいです。

1
2
const resp1 = await kintone.api('/k/v1/record', 'GET', params1);
const resp2 = await kintone.api('/k/v1/record', 'GET', {/* resp1によってparamを変える */});

Promiseとasync/awaitには次のような関係性があります。

  • awaitを使ってPromiseの処理の終了を待つことができる。
    すでに例で示してあるとおり、kintone.api()をawaitで待つことができます。
    それはkintone.api()はPromiseオブジェクトを返却しているためです。
  • async関数はPromiseを返却する。
    async関数はPromiseオブジェクトを返却します。
    そのため上記のようにthenとawaitを両方使い合わせることができます。
    ただし、混乱の元になりますので極端に書き方を混在させるようなことはしないほうがいいでしょう。

さいごに

2020年1月のアップデートで、フィールド変更時イベントを除くすべてのイベントがPromiseをサポートしました。
2020年1月版 主なアップデート (External link)
それまでは表示系のイベントなどはPromiseをサポートしておらず、非同期処理がPromiseで表現できませんでしたが、気軽にPromiseを使えるようになりました。
Promiseが使えるということはもちろんasync/awaitも利用できますので、他アプリのデータ取得など、非同期処理を使いこなしていきましょう。

PromiseはJavaScriptや非同期処理に慣れていないと難しい概念です。
async/awaitを利用しても、実際にはPromiseを操作することに代わりありませんが、コードの見通しが直感的になります。
最初はわからないこともあると思いますが、繰り返し使えば少しずつ分かってくます。
慣れていきましょう。

information

このTipsは、2020年4月版kintoneで動作を確認しています。