JavaScript

【JavaScript】非同期処理を並列処理する方法

Promiseオブジェクトとは?

Promiseオブジェクトは、処理が全て終わっているのかどうかを判定するために使われます。

使い方

var result = new Promise( コールバック関数 )

Promiseオブジェクトは上記のようにコールバック関数を定義します。初心者の方だとコールバック関数(≒無名関数)にアレルギー反応を起こすかと思いますが、これを機に少し理解を深めていきましょう。
(無名関数とは、名前のない関数のことで、function (){} の形をしています。名前がある関数は、funcrtion sample (){} みたいに書きますよね。)

関数の引数には、よく値を取ることが多いと思いますが、Promiseオブジェクトは処理の始まりと終わりを取得して判定するため関数を引数にとります。

var result = new Promise( function( resolve ) {
    resolve( "完了しました!" );
});
console.log( result ); // 完了しました!

Promise()の引数の関数内には、実行したい処理を記述します。
引数のresolveを上記のように使うことで、コールバック関数の終わりを認識し、戻り値を指定することもできます。

やってみる

とりあえず、簡単な例を作成してみようと思います。仕様は以下のような感じです。

・3秒・5秒・9秒かかる処理を用意
・それぞれの処理終了後にコンソールでメッセージを表示
・全ての処理が終わったタイミングでも完了メッセージを表示

準備

// setTimeout( 'コールバック関数', 'タイムアウト時間' );

function waitTime( sec ) {

    console.log( sec + "秒の処理を開始" );
    // resolve()が実行されたら戻り値を返す
    return new Promise( function( resolve ) {
        // sec秒後に処理を実行
        setTimeout( function() {
            console.log( sec + "秒かかる処理を完了" );
            // Promiseはresolveすると戻り値を返す
            resolve();
        }, sec * 1000 );
    });
}

まずは、〜秒後に処理を実行する関数を作成しました。この関数の戻り値は、Promiseオブジェクトを返します。Promiseオブジェクトとは、コールバック関数の始まりと終わりを認識し、関数の処理が完了しているのかどうかを判定できるオブジェクトでしたよね。

return new Promiseの部分は、resolve()されるまで戻り値を返しません。

waitTime( 3 );
waitTime( 5 );
waitTime( 9 );
console.log( "全ての処理が完了しました!" );

function waitTime( sec ) {
    /* 省略 */
}

次は5秒の処理
次は9秒の処理
全ての処理が完了しました!
3秒かかる処理を完了
5秒かかる処理を完了
9秒かかる処理を完了

では、実際に実行する部分を書いてコンソールで確認してみましょう。うまくいってないですよね?
これは、関数は順次呼び出されているけど、中身の処理の終了を待たずに次の処理へ進んでいるのが原因です。
つまり呼び出すだけ呼び出しといて、処理の終了を検知せずに進んでいるんです。なので、終了を検知する必要があります。

そこで使うのが、Promiseオブジェクトです。このオブジェクトを処理の戻り値として受け取ると、「待ってから!」という処理を記述できるようになります。

非同期処理を同期処理のように扱う

waitTime( 3 )
.then( (res) => {
    waitTime( 5 )
    .then( (res) => {
        waitTime( 9 )
        .then( (res) => {
            console.log( "全ての処理が完了しました!" );
        })
    })
})

function waitTime( sec ) {
    /* 省略 */
}

3秒の処理を開始
3秒かかる処理を完了
5秒の処理を開始
5秒かかる処理を完了
9秒の処理を開始
9秒かかる処理を完了
全ての処理が完了しました!

うまくいってますね。非同期で時間のかかる処理の部分を同期的な処理として扱えてます。
次に、async/awaitで書き換えてみます。

main();

async function main() {
    await this.waitTime( 3 );
    await this.waitTime( 5 );
    await this.waitTime( 9 );
    console.log( "全ての処理が完了しました!" );
    return;
},

function waitTime( sec ) {
    /* 省略 */
}

呼び出す関数の前にawaitをつけると、その関数が戻り値を返すまで待機します。また、awaitは、常にPromiseオブジェクトを返すasync関数の中でしか使えないので、main()という非同期関数を作成しました。

ただし、waitTime()のようにPromiseオブジェクトを返すためにresolve()を使っているようなものをasyncで書き換えることはできません。

複数の非同期処理を並列処理する

Promise.all( Promiseオブジェクトの配列 );

今回の目標は時間のかかる処理を同時にスタートさせて、全ての処理が終わり次第次の処理を実行することです。
それを実現するには非同期処理を同時に実行する必要があり、それを可能にするのがPromise.all()です。
引数には、Promiseオブジェクトを配列形式で渡します。

main();

async function main() {
    // 同時実行する関数の配列
    var all_process = [
        this.waitTime( 3 ),
        this.waitTime( 5 ),
        this.waitTime( 9 )
    ]
    // 同時実行開始, awaitで全て完了したことを検知
    await Promise.all( all_process );
    console.log( "全ての処理が完了しました!" );
    return;
},

function waitTime( sec ) {
    /* 省略 */
}

3秒の処理を開始
5秒の処理を開始
9秒の処理を開始
3秒かかる処理を完了
5秒かかる処理を完了
9秒かかる処理を完了
全ての処理が完了しました!

戻り値は、Promiseオブジェクトを返すので、awaitをつけて全ての処理が完了することを検知させます。

おしまい。

COMMENT

メールアドレスが公開されることはありません。