ES6 Promiseチェインを複数使った非同期処理の効率化

## 例えば

10個のオーディオファイルを取り出して、順番に1個ずつエンコードし、演奏をするようなアプリケーションを考える。エンコードと演奏はそれぞれ非同期で処理される。

```
1個目のエンコード->演奏->2個目のエンコード->演奏...10個目のエンコード->演奏　
```

コードで書くと以下となる。

```js

// オーディオファイル10個
var audioFiles = 
['audiofile00','audiofile01','audiofile02','audiofile03','audiofile04',
 'audiofile05','audiofile06','audiofile07','audiofile08','audioFile09'];

// エンコード
function encode(file) {
    console.log(file + ' エンコード開始.')
    return new Promise( (resolve,reject)=> {
        // 疑似的な処理 実際はここにエンコード処理が入る
        setTimeout(()=>{console.log(file + ' エンコード完了.'); resolve(file);},500);
    });
}

// 演奏
function play(file){
    console.log(file + ' 演奏開始.')
    return new Promise((resolve,reject)=>{
        // 疑似的な処理 実際はここに再生コードが入る
        setTimeout(()=>{console.log(file + ' 演奏終了.');resolve();},2000);
    });
}

//　エンコード => 演奏実行
var promiseChain = Promise.resolve(0);
audioFiles.forEach((file)=>{
    // エンコードが終わったら、演奏を開始する
    promiseChain = promiseChain.then(encode.bind(null,file)).then(play);
});
```

実行結果は以下の通りである。期待通りの逐次処理である。

```
audiofile00 エンコード開始.
audiofile00 エンコード完了.
audiofile00 演奏開始.
audiofile00 演奏終了.
audiofile01 エンコード開始.
audiofile01 エンコード完了.
audiofile01 演奏開始.
audiofile01 演奏終了.
audiofile02 エンコード開始.
audiofile02 エンコード完了.
audiofile02 演奏開始.
audiofile02 演奏終了.
audiofile03 エンコード開始.
audiofile03 エンコード完了.
audiofile03 演奏開始.
audiofile03 演奏終了.
audiofile04 エンコード開始.
audiofile04 エンコード完了.
audiofile04 演奏開始.
audiofile04 演奏終了.
audiofile05 エンコード開始.
audiofile05 エンコード完了.
audiofile05 演奏開始.
audiofile05 演奏終了.
audiofile06 エンコード開始.
audiofile06 エンコード完了.
audiofile06 演奏開始.
audiofile06 演奏終了.
audiofile07 エンコード開始.
audiofile07 エンコード完了.
audiofile07 演奏開始.
audiofile07 演奏終了.
audiofile08 エンコード開始.
audiofile08 エンコード完了.
audiofile08 演奏開始.
audiofile08 演奏終了.
audioFile09 エンコード開始.
audioFile09 エンコード完了.
audioFile09 演奏開始.
audioFile09 演奏終了.
```

## 課題と解決方法

逐次処理で行うと、1曲あたりの演奏時間が2秒でエンコード時間は演奏時間より短いものの1個あたり500msecかかるとすれば演奏の間に500msecの無音時間が含まれる。この無音時間をできる限りなくしたい。

無音時間はこのコードでは500msだから大したことないけど、実際は数分の曲をエンコードするのに数秒以上かかるのでとても気になる。

選ばれた10個を先にエンコードしてキャッシュしておき、全部エンコードし終わった段階で演奏すれば曲間の無音時間はすべてなくなるが、そのかわり開始時に最大5秒待たされることになる。

最初のエンコードにかかる時間はしょうがないにしても、最初の曲を演奏している間に2曲目以降のエンコードを終わらせておけば2曲目以降は曲間の無音時間なし演奏できるようなる。非同期処理をうまく使うことになると思うしね。この方法でいこうかなと思う。

## 実装

さて、方法はわかったので実装するにはどうしたらよいか考えた。結論は「`Promise`チェインをエンコード用・演奏用の2つ作り、エンコード用のチェインから演奏用のチェインをコントロールする」である。

まず実際のコードを次に示す。

```js


// オーディオファイル10個
var audioFiles = 
['audiofile00','audiofile01','audiofile02','audiofile03','audiofile04',
 'audiofile05','audiofile06','audiofile07','audiofile08','audioFile09'];

// エンコード
function encode(file) {
    console.log(file + ' エンコード開始.');
    return new Promise( (resolve,reject)=> {
        // 疑似的な処理 実際はここにエンコード処理が入る
        setTimeout(()=>{console.log(file + ' エンコード完了.'); resolve(file);},500);
    });
}

// 演奏
function play(file){
    console.log(file + ' 演奏開始.');
    return new Promise((resolve,reject)=>{
        // 疑似的な処理 実際はここに再生コードが入る
        setTimeout(()=>{console.log(file + ' 演奏終了.');resolve();},2000);
    });
}

//　エンコード・演奏実行
var playChain = Promise.resolve();
var encodeChain = Promise.resolve();

audioFiles
.forEach((file)=>{
    //　エンコードチェイン
    encodeChain = encodeChain
    .then((f)=>{
        if(f){
            // 演奏チェイン
            // エンコードが完了したら演奏チェインに追加
            playChain = playChain.then(play.bind(null,f));
        }
        return encode(file);
    });
});

// 最後の演奏処理
encodeChain
.then((f)=>{
    if(f){
        // 再生チェイン
        playChain = playChain.then(play.bind(null,f));
    }
});

```

これを実行すると、こうなる。

```
audiofile00 エンコード開始.
audiofile00 エンコード完了.
audiofile01 エンコード開始.
audiofile00 演奏開始.
audiofile01 エンコード完了.
audiofile02 エンコード開始.
audiofile02 エンコード完了.
audiofile03 エンコード開始.
audiofile03 エンコード完了.
audiofile04 エンコード開始.
audiofile00 演奏終了.
audiofile01 演奏開始.
audiofile04 エンコード完了.
audiofile05 エンコード開始.
audiofile05 エンコード完了.
audiofile06 エンコード開始.
audiofile06 エンコード完了.
audiofile07 エンコード開始.
audiofile07 エンコード完了.
audiofile08 エンコード開始.
audiofile01 演奏終了.
audiofile02 演奏開始.
audiofile08 エンコード完了.
audioFile09 エンコード開始.
audioFile09 エンコード完了.
audiofile02 演奏終了.
audiofile03 演奏開始.
audiofile03 演奏終了.
audiofile04 演奏開始.
audiofile04 演奏終了.
audiofile05 演奏開始.
audiofile05 演奏終了.
audiofile06 演奏開始.
audiofile06 演奏終了.
audiofile07 演奏開始.
audiofile07 演奏終了.
audiofile08 演奏開始.
audiofile08 演奏終了.
audioFile09 演奏開始.
audioFile09 演奏終了.
```

演奏しながらエンコードすることで、目標が達成できていると思う。演奏順序とエンコードの順序は保たれているし、エンコードを行った後に再生を行う要件も満たしている。処理のキモはエンコードが完了したタイミングで再生チェインに追加するところだろうか。

仮にエンコード処理が長くかかり、演奏開始時間までに間に合わなかったとしても`Promise.then()`でうまく吸収し、演奏開始を遅らせてくれる。これは`Promise.then()`が`Promise`の状態に合わせて挙動を変えてくれるからである。