目指せ!JavaScript カスタマイズ中級者(2)〜Promise のかわりに async/await 編〜

目次

はじめに

前回の、 目指せ!JavaScript カスタマイズ中級者(1)〜webpack 編〜 では webpack の導入について説明しました。
webpack を導入することで新しい記述が使えるのは前記事でお伝えしたとおりです。
今回は、新しい記述方法のひとつ async/await を使うことによって非同期処理を書きやすくしていきたいと思います。

目指せ中級者シリーズの記事一覧は以下を参照してください。
目指せ中級者シリーズ

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 で動作を確認しています。