Promiseでサブテーブルの特定行を非表示にする

はじめに
---
当方、kintoneを最近利用し始めまたばかりの初心者です。kintoneは、ノーコードで色々な業務ツールをクラウド上で作れて非常に優れモノだと感心していましたが、一般機能では実現できないことをJavaScriptでカスタマイズできることを知り、更に興味を持ち始めています。（JavaScriptを碌に知りもせずなのですが:sweat_smile:）

今回やりたかったことは、あるアプリ内に配置したサブテーブルに非表示チェックボックス列を置き、これをチェックすることでその行を非表示にしたいということです。

以下の[**cybozu developer Community**](https://community.cybozu.dev/ "cybozu developer Community")のご解答を参考にさせていただき、解決に至るまでに得た知識を簡単にまとめてみました。

https://community.cybozu.dev/t/topic/7729

解答反映前のコード
---
上記解答を反映させる前の動作しないコードは以下のとおりです。
今回対象のコントロールのフィールドコードは、サブテーブルが`documentInfo`、非表示チェックボックスが`isHidden`となります。デバッグ用の`console.log`はそのままにしています。

```js
(function() {
  'use strict';
  
  kintone.events.on('app.record.detail.show', function(event) {
    let record = event.record;
    let tableData = record.documentInfo.value;
    
    let dTable = kintone.app.record.getFieldElement('documentInfo');
    let dtBody = dTable.tBodies[0];
    let dtRows = dtBody.rows;    
    console.log('tableData.length=' + tableData.length);
    for (let i = 0; i < tableData.length; i++) {
      console.log(i + '回目');
      if (tableData[i].value.isHidden && tableData[i].value.isHidden.value[0]) {
        if (dtRows) {
          console.log('dtRows[' + i + '] はイケてる');
          dtRows[i].style.display = 'none'
        } else {
          console.log('dtRows[' + i + '] がundefined');
        }
      }
    }
    return event;
  });

})();
```
以下で`style`が`undefined`になってしまい`none`を設定できません。
```js
dtRows[i].style.display = 'none'
```

行き詰っていたところ上記解答を見つけましたが、これを反映させるには、そこに登場する`Promise`の理解が必要でした。

Promiseとは？
---
以下が解答のコードです。JavaScript初心者の自分には、最初は何をしているのかさっぱりわかりませんでした。

```JS

const trDrawWait = (table, tableClass) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      try {
        let recordData = kintone.app.record.get(), rec = recordData.record;

        if ((rec[table].value.length + 1) === Array.from(document.getElementsByClassName(tableClass))[0].querySelectorAll('tr').length) {
          resolve();
        } else {
          resolve(trDrawWait(table, tableClass));
        }
      } catch(error) {
        resolve();
      }
    }, 500);
  });
};
```
`Promise`とは、JavaScriptの組み込みオブジェクトで、コンストラクタ関数（クラスのようなもの）です。コンストラクタ関数とは、関数の構文で定義し、生成されたインスタンスがクラスのように振る舞うものです。
`Promise`が何をしているのか、以下に`Promise`の生成を単純化したコードを示します。

```js
let hoge = new Promise(function(resolve, reject) {
  // 通常は非同期処理

 // 成功時
  resolve();
  // 必要なら失敗時
  // reject();
});

hoge.then(function(value) {
    // 処理成功時
}).catch(function(error) {
    // 処理失敗時
});
```
+ `new`で`Promise`オブジェクトを生成。
+ `Promise`の引数として渡す関数のことを`executor`と言う。
+ `executor`は、通常は非同期関数を含んだものを渡す。
+ `executor`は、必ずしも非同期関数を含んでいなくても良い。
+ `then`は`executor`内で`resolve`が呼び出されないと実行されない。
+ 同様に`catch`は`executor`内で`reject`が呼び出されないと実行されない。
+ `executor`の引数はその順番が重要で、第一引数が`resolve`、第二引数が`reject`と定められている。
+ `resolve`と`reject`の名前は自由に変えられる。

また、これは今回のケースとは関係ないですが、

+ `resolve`に`resolve(data)`のように引数を渡すと`then`で定義する関数の引数で受け取れる。
+ これは`reject`～`catch`の関係についても同様。
+ `then`は複数を連ねることができる。これをPromiseチェーンと言う。
+ `catch`は一つのみで複数を連ねることはできない。

解答コードについて、
+ setTimeoutという非同期関数により非同期処理が行われる。
+ レコードデータ数とサブテーブルの`tr`要素数が一致することで解決処理を実行する。
（`tr`要素のレンダリングの遅延に対応したもの）
+ 行数が不一致となる場合、自分自身を再帰呼出しする。
+ `try`～`catch`の`catch`は、`Promise`の`catch`メソッドとは別物。

反映させてみる
---
実際に自分のアプリにこの解答を反映させた最終コードが以下です。

```JS
(function() {
  'use strict';
  
  let count = 1;
  const trDrawWait = (table, tableClass, depth = 0, maxDepth = 1000) => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        try {
          let recordData = kintone.app.record.get(), rec = recordData.record;

          if ((rec[table].value.length + 1) === Array.from(document.getElementsByClassName(tableClass))[0].querySelectorAll('tr').length) {
            console.log(count);
            resolve();
          } else if (depth >= maxDepth) {
            console.log("最大ネスト数に達しました。")
            resolve();  
          } else {
            console.log(count++);
            resolve(trDrawWait(table, tableClass, depth + 1, maxDepth));
          }
        } catch(error) {
          resolve();
        }
      }, 500);
    });
  };

  kintone.events.on('app.record.detail.show', function(event) {
    let record = event.record;
    let tableData = record.documentInfo.value;
    
    // 2つ目の引数はサブテーブルのクラス名。ブラウザのデバッグ機能で確認
    trDrawWait('documentInfo', 'subtable-gaia subtable-5520749 show-subtable-gaia').then(() => {
      let dTable = kintone.app.record.getFieldElement('documentInfo');
      let dtBody = dTable.tBodies[0];
      let dtRows = dtBody.rows;    
      console.log('tableData.length=' + tableData.length);
      for (let i = 0; i < tableData.length; i++) {
        console.log(i + '回目');
        if (tableData[i].value.isHidden && tableData[i].value.isHidden.value[0]) {
          if (dtRows) {
            console.log('dtRows[' + i + '] はイケてる');
            dtRows[i].style.display = 'none'
          } else {
            console.log('dtRows[' + i + '] がundefined');
          }
        }
      }
    });
    
    return event;
  });

})();
```
若干書き換えていますが、ほぼパクリです。再帰呼出の回数制限を設けてみました。

解答者様並びに[**cybozu developer Community**](https://community.cybozu.dev/ "cybozu developer Community")に集う皆々様には感謝いたします。お陰様で問題を解決できました。本当にありがとうございます:bow_tone1:
kintoneはノーコードが売りですが、これからも皆様の助けお借りしつつ、カオスなJavaSriptと戯れる場所として、kintoneカスタマイズを利用したいと思っています:laughing:

参考リンク
---
https://ja.javascript.info/async

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise