Javascriptメモ：BootstrapのCollapseのトランジションを、async/awaitで待つ

Bootstrapのcollapseの`.show()`や`.hide()`メソッドは、トランジションのアニメーションを待たない。
collapse要素が表示され（消え）終わった**後に**何かしたい時はどうすればいいか。

[Bootstrapドキュメント：Collapse#Event](https://getbootstrap.jp/docs/5.0/components/collapse/#events)によれば、**トランジションが終了した時にイベントが発生**するから、それを捕まえればいいよ、とのこと。

# イベントリスナを書いた場合

* 画面にcollapse要素が２つあって、
* 片方を閉じるアニメーションが終わった後に、もう一方を開くアニメーションを始めたい

とする。

```javascript
// bootstrapは読み込み済みの前提。

// collapse要素が画面に２個あるとして
const fig1 = document.getElementById('collapse-fig1');
const fig2 = document.getElementById('collapse-fig2');

// それぞれのcollapseオブジェクト取得して
let myCollapse = {};
myCollapse['fig1'] = new bootstrap.Collapse(fig1, {toggle:false});
myCollapse['fig2'] = new bootstrap.Collapse(fig2, {toggle:false});

// これだと閉じる＆開くアニメーションが同時進行してしまう
// myCollapse['fig1'].hide()
// myCollapse['fig2'].show()

// fig1->fig2に切り替えたい
function switchFig() {
    myCollapse['fig1'].hide();      // (1) fig1を閉じる
}

// fig1の閉じるトランジションが終わったら
fig1.addEventListener('hidden.bs.collapse', () => {
    myCollapse['fig2'].show();     // (2) fig2を開く
});
```

確かに動作する。

上記ぐらいシンプルなら、これでもいい。
でも、こういうスクリプトが必要ということは、実際には条件分岐やら何かもっとガチャガチャしたコードになってるのだ。

switchFig関数がやりたいことは画面の切り替えであり、本来、**(1)閉じる処理と(2)開く処理はセット**。
なのに、**関数とイベントリスナのコールバックに分断**されてしまっている。

後日何か変更するときに、イベントリスナもセットだと思い出せるだろうか。
思い出せたとして、コードのどこかのイベントリスナを**探すのめんどいなー**ってならないだろうか。
私は思い出せないし、めんどい。

# async/awaitを使った場合

ここは、コールバック地獄の蜘蛛の糸、Promiseにすがって、async/awaitを使いたい。
（IEには対応できないけど、もういいよね？）

まずは、「おまけつきcollapse」を作れるようにする。

```javascript
// おまけつきcollapseを作る関数
const makeMyCollapse = (id) => {

    // ターゲットとなるcollapse要素。存在しないIDを指定されない前提。
    const target = document.getElementById(id);

    // bootstrap の collapse を取得。Collapseじゃない要素を指定されない前提。
    let collapse = new bootstrap.Collapse(target, {toggle:false});

    let fire;        // 関数が入る予定
    let registed;    // イベント登録済みフラグ

    // おまけ。collapse閉じて待つ（promiseを返す）メソッド。
    collapse.waitHide = () => {

        // イベントリスナ未登録なら登録
        if (!registed) {
            // targetにhidden.bs.collapseイベントが発生したらfire！
            target.addEventListener('hidden.bs.collapse', () => {
                if (fire) fire();
            });
            registed = true;    
        }

        // 閉じて
        collapse.hide();

        // プロミスを返す
        return new Promise(resolve => {
            // fireに、このプロミスを解決する関数を設定
            // つまり、fire()が実行されたときに、このプロミスは解決する。
            fire = () => resolve();
        });
    }

    // 「おまけつきcollapse」を返す
    return collapse;
}
```

この準備をしておけば、処理のコードは以下のようになる。

```javascript
// 要素のIDを渡して、おまけつきcollapseを取得
let myCollapse = {};
myCollapse['fig1'] = makeMyCollapse('collapse-fig1');
myCollapse['fig2'] = makeMyCollapse('collapse-fig2');

// fig1->fig2に切り替えたい
async function switchFig() {
    await myCollapse['fig1'].waitHide();  // (1) 閉じるのを待って
    myCollapse['fig2'].show();            // (2) その後、開く
}
```

switchFig関数が何をしているのか、コメント不要レベルの明白さになった。
将来の自分のために、こちらを採用したい。

## 参考

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

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/await