nodejs でArrayに入っているコマンドを map, exec で非同期処理し、結果をまとめる

**@kenmaro**です。
普段は主に**秘密計算、準同型暗号などの記事について投稿しています**。
秘密計算に関連するまとめの記事に関しては以下をご覧ください。

> - [秘密計算エンジニアを始めて１年が経った。](https://qiita.com/kenmaro/items/416657efca2ff296169b)
-  [秘密計算エンジニアを始めて２年半が経った。](https://qiita.com/kenmaro/items/48059dd60f79e80f5ef8)


## 概要
今回は秘密計算等の記事ではなく、開発時の備忘録となります。

少し前になりますが、
これができなくてげきハマりしたので、誰かの役に立つのではないかと思い、備忘録化しておきます。

## やりたかったこと
nodejs で express サーバを用い、以下のような処理をするAPIを作っていました。

1. クライアントからリクエストを取得する
2. azure へとサブプロセスでbashコマンドを用い、container instance のステータスを取得する（クラスターは複数）
3. 全ステータスを取得した段階で、firebase firestore にそれらのステータスを書き込む
4. フロントエンドではfirestore の変更をキャッチし、クラスターのステータスを更新する


## ハマったところ
この処理の、2において、
nodejs 側ではexec を用いてバッシュコマンドを実行するのですが、そのコマンドはarrayに入っており、
map関数を用いてそれらのコマンドを実行するように書いていました。

3に入るときには、２で全てのコマンドが実行され、それらの結果がarrayにまとめられている必要があったのですが、
2では非同期関数であるexec を実行しており、結果を全ての非同期関数の処理の終了を待った状態で集め、
その結果をfirestoreに書き込みたかったのですが、
mapのなかで非同期処理を行なった際に結果を集める前にfirestoreへの処理へと移行しており、
そこをどうやって待てばいいのか実装するまでに恥ずかしながらけっこうハマってしまいました。。


## 解決方法

このように書くことでやりたかった処理を実装することができました。

```javascript
const refreshCluster = async (req, res, next) => {
    const clusterId = req.params.cluster_id;
    const command = `
    az container list --resource-group ${clusterId}
    `

    try{
        const out = execSync(command);
        const tmp = out.toString();
        const tmp2 = eval(tmp);

        const startCommands = []
        tmp2.map((cluster_info) => {
            const tmp_command = `
            az container show --name ${cluster_info.name} --resource-group ${clusterId} --query "containers[].{name:name, status:instanceView.currentState.state}"
            `
            startCommands.push(tmp_command)
        })

        console.log("debug1");

        
        const util = require('util');
        const childProcess = require('child_process');
        const execPromised = util.promisify(childProcess.exec);
        console.log("debug2");


        async function execWrap(startCommands) {
            const resTotal = startCommands.map(async cmd => {
                console.log(`executing ${cmd}`);
                //const out = execSync(cmd);
                const res = await execPromised(cmd);
                let tmp_res = JSON.parse(res.stdout.toString())[0];
                //statusResult[(tmp_res.name).replace(/-/g, '_')] = tmp_res.status;
                return tmp_res;
            })

            return Promise.all(resTotal);
        }

        const test1 = await execWrap(startCommands);

        let statusResult = {};

        for(let i=0; i<test1.length; i++){
            statusResult[(test1[i].name).replace(/-/g, '_')] = test1[i].status;
        }


        console.log("here");
        console.log(statusResult);

        const cluster1 = await firestore.collection('cluster').doc(clusterId);
        const cluster2 = await cluster1.get();
        const cluster_data = cluster2.data();
        let updateData = {...cluster_data};

        
        Object.keys(statusResult).map((service_name) => {
            updateData["services"][`${service_name}`]["status"] = statusResult[`${service_name}`];
        })
        console.log(updateData);

        await cluster1.update(updateData);

        //cluster1.azureStorageDestroyTemplate(cluster_data);
        res.send('command passed to backend. please wait..');
        
        

    }catch(err){
        res.status(400).send(err.message);
        
    }
}

```

`utilpromisify` でexec をラップし、
結果が格納されるarray （ここでは`resTotal`)を`Promise.all(resTotal);`
と書くところが必要でした。

以下のリンクを参考にし実装することができました、
ありがとうございます。。


https://www.wakuwakubank.com/posts/728-nodejs-child-process/

https://qiita.com/mazxxxry/items/eb2036b28f75eb39333c

https://tech-1natsu.hatenablog.com/entry/2017/12/09/012717


## まとめ
今回はexec で実行するコマンドが複数入ったArrayをmapしてexecを実行するとき、
非同期処理で得られた結果についてきちんと処理が完了してから次の処理に移る方法について、備忘録的にまとめました。
もしどなたかの参考になれば嬉しいです。


今回はこの辺で。


@kenmaro