kintone Promise の基礎

# はじめに
kintoneをJavaScriptでカスタマイズしていると、時々意図していない動きに遭遇します。
その度に、「ああ、これは非同期処理なんだからPromiseを使って順番に処理する必要があるんだっけ」と、その度に"Promise"の書き方を確認して、デバッグしながら「何とか動いたので良しとしよう」とやってるのですが、もやもやが溜まるばかり。

今回、Promiseの基礎的な部分を自分なりに整理しておくことにしました。
「基礎であるが初歩では無い」あたりを埋めてみたいと思います。

# 対象者

* JavaScript以外のプログラムの経験が結構ある
* JavaScriptのプログラムをまあまあ書ける
* kintoneでJSカスタマイズができる

# kintone Promise とは？

まず、kintoneにPromiseが実装された背景などはこちらのリンクをご一読ください。
[kintone API で Promise を使ってみよう！](https://developer.cybozu.io/hc/ja/articles/204564604)

次の流れで、[kintone.Promiseとは](https://developer.cybozu.io/hc/ja/articles/215029846)
という記事がありますが、こちらを読んでも

>結論：kintone.Promiseとは
>な～んか処理の順番が上手くいかない時や、
>な～ぜか上手く処理が反映されない時に使うと解決できるかもしれないもの。

とあって、結局もやっとしたものが残ります。

参考のコードも売上管理・在庫管理などで難しい。

Promiseは売上管理や在庫管理の処理をする際に使うものか？と思ってしまいますが、意外と単純な処理でもPromiseを使わざるを得ない場合はあります(;-)


# JavaScript Promise

kintone Promiseの前に JavaScript Promise の理解が必要です。

* MDN [Promiseを使う](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Using_promises)

* (Promise)[https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise]

Promiseとは何かの定義は上記[Promise](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise)の方が分かりやすいかと思います。

解説から引用します

>Promise インターフェイスは作成時点では分からなくてもよい値へのプロキシです。
Promise を用いることで、非同期アクションの成功や失敗に対するハンドラーを関連付けることができます。
これにより、非同期メソッドは、最終的な値を返すのではなく、未来のある時点で値を持つ Promise を返すことで、同期メソッドと同じように値を返すことができるようになります。

つまり、ブラウザのイベント（例えばマウスをクリックしたとか）などの際の動作で発火するイベントリスナーの登録と同じように、Promiseを使って非同期処理の際の成功と失敗に対するハンドラーを事前に登録できると言うイメージです。

それではPromseの使い方を見ていきます。

# Promise() コンストラクター
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise

## Promiseを返す関数を作る

Promiseコンストラクターで関数をラップすることで、関数がPromiseを返すことができるようになります。

こんな感じ

 **Promiseを返す関数 promise1** 

```javascript
const promise1 = (value) => {
    return new Promise(resolve => {
      resolve(value.toUpperCase());
    })
};
console.log(promise1()); // Promise {}
```

作った関数をconsole.logすると、Promise と帰ってくるので、確かにPromiseを返す関数になっているようです。

## Promiseを返す関数を利用する

で、作った関数をどう使うかですが、これは下記のようにして使います。

```javascript
promise1('Hello').then(result => {console.log(result)});
```

冗長のようですが Promise というのはこういうものと思ってください。

関数が1つだけだと説明にならないのでもう一つ追加します。

 **Promiseを返す関数 promise2** 

```javascript
const promise2 = (value) => {
    return new Promise(resolve => {
      resolve(value.toLowerCase());
    })
};
```

## Promiseを繋げる

それから、promise1 と promise2 を順番に処理させたい時は下記のように繋ぎます。

```javascript
promise1('Hello')
.then(result1 => { // result1 に最初のPromise関数の結果が返ってくる
  console.log(result1); // 戻り値を使って何か処理する
  return promise2(result1) // ここで次のPromiseをリターンする
})
.then(result2 => { // 次のPromise関数の結果が返ってくる
  console.log(result2); // 戻り値を使って何か処理する
})
```

間に then() 関数を入れて繋げるだけです。（いわゆるPromiseチェーン）
簡単ですね。

* then() メソッド https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/then

then関数の中にコールバック関数を書くことでPromiseを返す関数を処理の順番に繋げることができます。

## エラー処理

エラー処理はPromiseを返す関数の中で処理しても良いですし、作った関数を使う側で下記のようにしても良いです。

```javascript
promise1('Hello')
.then(result1 => {
  console.log(result1);  
  return promise2(result1)
})
.then(result2 => {
  console.log(result1); // ここでエラーが発生
  console.log(result2);
})
.catch(error => { // ここでエラーをキャッチする
  console.log(error); // :ReferenceError: result1 is not defined
})
```

# kintone Promise 再び

まずサンプルコードです。

## サンプルコード

```javascript
(function() {
  'use strict';
  kintone.events.on('app.record.detail.show', (event) => {
    return new kintone.Promise((resolve) => {
      resolve(kintone.app.getId());
    })
    .then(result => {
      const params = {'app': result};
      return kintone.api(kintone.api.url('/k/v1/app/form/fields', true), 'GET', params);
    })
    .then(result => {
      const fields = getFields(result, 'GROUP');
      fields.forEach(field => {kintone.app.record.setGroupFieldOpen(field.code, false)});
      // return event; // 処理によってはeventをリターンしなくてもOK
    })
    .catch(error => {
      console.error(error);
    });
  });

  function getFields (fields, fieldType) {
      let result = [];
      try {
          const values = Object.values(fields.properties);
          if (fieldType) {
              values.map((field) => {
                  if (field.type === fieldType) {
                      result.push(field);
                  }
              });
          } else {
              result = values;
          }
          return result;
      } catch (error) {
          return error;
      }
  }
})();
```

## kintoneのエラーを判別して処理する場合

```javascript
  // 最初の処理
  // fetchRecordsはkintoneに何か処理をする関数
  fetchRecords(URL, option)
  .then(result => {
    if (result.getResponseCode() !== 200) { // ステータスコードが200以外の時
      throw new Error(result.getContentText()); // エラーをスローする
    }
    return result;
  })
  .then(result => {
    // 次の処理
    console.log(result.getContentText());
  })
  .catch(error => {
    // スローされたエラーキャッチして処理する
    console.error(error);
  });
}
```

## kintone.Promise CheatSheet

kintoneのPromiseの書き方については、デベロッパーサイトにもいくつかの書き方がありどれにすれば良いのか迷いますが、
まずは基本の書き方１つで行きましょう。

テンプレートを載せておきます。


```
  kintone.events.on('<kintoneイベント>', (event) => {
    return new kintone.Promise((resolve) => {
      // 何か処理
      resolve(<次に渡したい結果など>); // Promiseチェーンで繋げたい結果はresolve()で返却する
    })
    .then(result => {
      // 何か処理
      // REST APIでレコードを取得など。 下記の書き方をすれば、処理結果がPromiseで返される。
      return kintone.api(kintone.api.url('<REST API エンドポイント>', true), '<GET/POST>', <パラメータ>);
    })
    .then(result => {
      // 何か処理
      return event; // 結果をレコードに反映するなどの処理に応じて、最後にeventをリターンする。
    })
    .catch(error => {
      console.error(error);
    });
  });
```

上記のテンプレートを使ってもらえれば、下記のようにPromiseがネストしていくような書き方にならずに済むかと。。。:sweat_smile:

```
    .then(result => {
      return kintone.api(kintone.api.url('<REST API エンドポイント>', true), '<GET/POST>', <パラメータ>)
      .then(result => {
        return kintone.api(kintone.api.url('<REST API エンドポイント>', true), '<GET/POST>', <パラメータ>)
        .then(result => {
          return kintone.api(kintone.api.url('<REST API エンドポイント>', true), '<GET/POST>', <パラメータ>)
          .then(result => {

```

# 参考

* https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise
* https://developer.cybozu.io/hc/ja/search?label_names=Promise
* https://qiita.com/sy250f/items/bd28093c6ffabb1186db
* https://jsprimer.net/basic/async/