echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【Javascript】非同期処理のPromiseやasync/awaitについて その1

Promiseについて

Promiseとは、

  • 非同期処理を制御するためのしくみ
  • ES2015以降で使用可能

メリット

その1. コードが簡潔になり、見やすくなる。(個人差あり)

例えば、非同期関数func1〜func3があったとして、下記の制約がある場合、

  • func2はfunc1の結果が必要
  • func3はfunc2の結果が必要

一般的なコールバック関数を使用した場合、ソースは下記のようになる。
(典型的な「コールバック地獄」というやつです)
※しかもエラー処理を省略してるので、エラー処理も書いたらもっと煩雑になる...

function main() {  
    func1((err, data) => {
        func2(data, (err2, data2) => {
            func3(data2, (err3, data3) => {
                console.log(data3);
            });
        });
    });
}

これがPromiseを使えば、例えばこうなります。

// resolveの引数は、正常終了した際の戻り値。  
// rejectの引数は、エラー発生した際の戻り値。

function funcPromise1() {
    return new Promise(function(resolve, reject) {
        func1((err, data) => {
            if(!err) {
                resolve(data);
            } else {
                reject(err);
            }
        });
    });
}  

function funcPromise2(data) {
    return new Promise(function(resolve, reject) {
        func2(data, (err, data2) => {
            if(!err) {
                resolve(data2);
            } else {
                reject(err);
            }
        });
    });
}  

function funcPromise3(data2) {
    return new Promise(function(resolve, reject) {
        func3(data2, (err, data3) => {
            if(!err) {
                resolve(data3);
            } else {
                reject(err);
            }
        });
    });
}  

function main() {  
    funcPromise1.then((data) => {return funcPromise2(data);})  
        // resolveされた場合、then()の引数にはresolveの引数が格納される。  
        // rejectされた場合、catch()の引数にはrejectの引数が格納される。
        .then((data2) => { return funcPromise3(data2); })
        .then((data3) => { console.log(data3); })
        .catch((err) => { console.log(err); });
}

funcPromise1~funcPromise3の関数定義は増えましたが、ソース自体は見やすくなったと思います。

エラー処理が簡潔になる

(先程のソースで若干ネタバレしてますが)func1〜func3のいずれかでエラーが発生しても、Promiseなら最後の「catch(err)」でまとめてエラー処理ができます。

これがコールバック関数の場合、こうなります。
(クッソ見にくい...というか、try~catchを使った場合、全体を囲ってtry~catchではエラー捕捉ができない(=毎回外側にthrowしなくちゃいけない)ので、もっと大変なことに。)

function main() {  
    func1((err, data) => {
        if(!err) {
            func2(data, (err2, data2) => {
                if(!err2) {
                    func3(data2, (err3, data3) => {
                        if(!err3) {
                            console.log(data3);
                        } else {
                            console.log(err3);
                        }
                    });
                } else {
                    console.log(err2);
                }
            });
        } else {
            console.log(err2);
        }
    });
}

非同期処理の並行実行の制御ができる

Promiseを使った場合、例えば複数の非同期処理を同時実行した場合に、下記のような処理が可能です。

    
function main() {  
    // Promise.race()は、引数の非同期処理のうち、
    // いずれか1個の処理が完了したら次へ進む
    Promise.race([funcPromise, funcPromise2, funcPromise3])
         // 最初に終わった処理がresolveの場合、thenの引数dataに
         // その処理のresolve値が格納される。
         // また最初に終わった処理がrejectの場合、catchの引数errに
         // その処理のreject値が格納される。
        .then((data) => { console.log(data); })
        .catch((err) => { console.log(err); });
  
    // Promise.all()は、引数の非同期処理がすべて終了するまで次へ進まない。
    Promise.all([funcPromise, funcPromise2, funcPromise3])
        // 引数data~data3には、それぞれfuncPromise~funcPromise3の
        // resolve値が格納される。(すべてresolveしないとthen()は実行されない)
        .then(([data, data2, data3]) => { 
            console.log(data); 
            console.log(data2);
            console.log(data3);
        })
        // いずれか1つでもrejectされた場合、最初にrejectされた値の
        // reject値が格納される。
        .catch((err) => { console.log(err); });
}

そして、話はasync/awaitにつながる訳ですが、それはまた後日。