JavaScriptのPromiseなんとなく使っているけど実は理解できていないかもという人やJavaScriptを始めたばかりの人を対象に一人でもPromiseを理解してもらえるように身近な例とシンプルなコードを使って説明を行っていきます。

Promiseを説明する場合は同期と非同期といった言葉と一緒に解説されることが多く、Promiseよりも非同期部分との説明で理解が混乱している人が多いので同期、非同期とPromiseの関係を忘れるとPromiseはシンプルなことがわかります。

JavaScriptのPromiseとは

身近にありそうな例を使ってPromiseとはどんなものかを理解していきましょう。

Promiseという英単語の日本語訳を学生時代に”約束”と覚えた人も多いかと思います。プログラムで使われる機能の名前とその名前の意味からつけられているので”約束”という言葉を使ってPromiseを確認していきます。

明日友人と近所のカフェで10時に待ち合わせの約束をします。約束には、約束通り10時に行く場合と約束を破り10時に行かない場合の2つの結果が存在します。Promiseもこの約束と同様に約束を守る結果(これをPromiseではresolve)と約束を守らない結果(これをPromiseではreject)の2つの結果を持ち、どちらかの結果を戻すことができます。

Promiseのコード化

”Promiseが2つの結果を持ちどちらかの結果を戻すことができる”とはどういうことか確認するためにPromiseの実際のコードを使って解説していきます。

Promiseオブジェクトをnew演算子を利用してインスタンス化し変数yakusokuに代入します。


let yakusoku = new Promise()

Promiseの引数には2つのコールバック関数resolveとrejectが入ります。


let yakusoku = new Promise(function(resolve,reject){
   //ここに処理を記述する 
})

最後に約束を守った場合と守らなかった場合のコードを追加します。 約束を守った場合はresolveに値を入れ、守らなかった場合にはrejectに値を入れます。


const keep_promise = true

const yakusoku = new Promise(function(resolve,reject){

    if (keep_promise){
        resolve('約束通りついたよ。')
    } else {
        reject('約束破ってごめん。')
    }
    
})

これでPromiseで約束をコード化することは完了です。

100%約束通りにつくと言い切れる人なら下記のように記述することも可能です。


const yakusoku = new Promise(function(resolve){
    resolve('いつも約束通りつくよ。')
})

Promiseを使う(約束を守った場合)

Promiseを作成することができたのでこのPromiseを使って約束を守った場合の動作確認を行なっていきます。作成した変数yakusokuにthenをつけることでPromiseのresolveの結果を取得することができます。thenの中には関数が入り、その引数にはresolveの結果が入ります。

keep_promiseの値はtrueで約束を守った場合です。


// keep_promise = trueの場合
yakusoku.then(function(comment){
    console.log(comment)
})
ここでは受け取る値をcommentとしていますが、任意の名前をつけてください。

実行すると”約束通りついたよ。”が表示されます。


約束通りついたよ。

文字列ではなくresolveに入れる値が配列の場合はどのようになるかも確認しておきましょう。


if (keep_promise){
    resolve(['a','b','c'])
} else {
    reject('約束破ってごめん。')
}

結果は、配列が取得できます。


[ 'a', 'b', 'c' ]

Promiseを使う(約束を守らなかった場合)

約束を守らなかった場合(keep_promise = false)にどうやってrejectに入れた値を取得するのか確認します。約束を守らなかった場合はthenではなくcatchを使うことでrejectの値を取得することができます。


// keep_promise = falseの場合
yakusoku.then(function(comment){
            console.log(comment)
        })
        .catch(function(comment){
            console.log(comment)
        })

実行すると”約束破ってごめん。”が表示されます。


約束破ってごめん。

catchを利用しなくてもthenに2つの関数を入れて、rejectの値を取得することもできます。


yakusoku.then(function(comment){
                console.log(comment)
            },
            function(comment){
                console.log(comment)
            })sole.log(comment)
        })

上記では約束を守るとthenの最初の関数が実行され、約束を破ると2つ目の関数が実行され、コメントが表示されます。

Promiseのステータスとは

Promiseは、3つのステータスを持っています。pending、resolved, rejectedの3つです。これまでに出てきた単語(resolve, reject)からresolvedは約束を守ってresloveの値が戻された場合のスターテス、約束が守られずrejectの値が戻された場合のステータスというのがわかります。

ChromeのデベロッパーツールでPromiseのステータスを確認することができます。

約束を守ったresolvedの場合
約束を守ったresolvedの場合
約束を守らなかったrejectedの場合
約束を守らなかったrejectedの場合

Pendingステータスとは

もうひとつステータスpendingがどのようなものか確認していきます。pendingは決定を待っている状態という意味があり約束の例を使うと約束の時間がまだ来ていない状態です。つまり約束を守るresolveなのか守れないrejectのかわからない状態です。

pendigの状態を確認するためにsetTimeout関数を利用して、5秒後にresolveとrejectの結果を取得するように変更を行います。


const keep_promise = false

const yakusoku = new Promise(function(resolve,reject){
    setTimeout(function(){
        if (keep_promise){
            resolve('約束通りついたよ。')
        } else {
            reject('約束破ってごめん。')
        }
    },1000*5)
})

デベロッパーツールを見ると上記では5秒後にresolveかrejectが戻されるのでその間はpendingのステータスとなります。

pendingのステータスを確認
pendingのステータスを確認

ここまでの動作確認でPromiseの3つのステータスの意味が理解できたのではないでしょうか。

Promiseを連結する方法

Promiseはチェーンでつなげて処理連結することができます。連結する方法を確認していきます。

新たにcatchTrainという変数を作成し、関数を設定します。関数は引数を受け取りreturnでPromiseを戻します。on_scheduleという変数の値によってresolveで戻すかrejectで戻すかが決まります。

returnでPromiseを戻すことを忘れないでください。

const catchTrain = function(comment){
    return new Promise(function(resolve,reject){
        if (on_schedule){
            resolve(comment + '10時2分の電車に乗ろう!')
        } else {
            reject(comment + 'でも今日は電車遅れてるね。')
        }
    })
}

先程作成したyakusokuのコードにcatchTrainを追加します。


const keep_promise = true
const on_schedule = true

const yakusoku = new Promise(function(resolve,reject){
        if (keep_promise){
            resolve('約束通りついたよ。')
        } else {
            reject('約束破ってごめん。')
        }
})

const catchTrain = function(comment){
    return new Promise(function(resolve,reject){
        if (on_schedule){
            resolve(comment + '10時2分の電車に乗ろう!')
        } else {
            reject(comment + 'でも今日は電車遅れてるね。')
        }
    })
}

実行する際は下記のようにthenを利用して連結することができます。catchTrainはyakusokuのresolveの値を受け取り内部の処理を実行します。catchTrainの処理でresolveの値が戻されたらその下のthenが実行され、console.logにコメントが表示されます。yakusokuとcatchTrainのどちらでrejectが戻されてもcatchが実行されます。


yakusoku.then(catchTrain)
        .then(function(comment){
            console.log(comment)
        })
        .catch(function(comment){
            console.log(comment)
        })

keep_promiseとon_scheduleの値によって表示される内容が異なるので確認していきましょう。

どちらもtrueの場合は下記のように表示されます。


//const keep_promise = true
//const on_schedule = true
約束通りついたよ。10時2分の電車に乗ろう!

on_scheduleがfalseの場合は下記のように表示されます。


//const keep_promise = true
//const on_schedule = false
約束通りついたよ。でも今日は電車遅れてるね

keep_promiseがfalseの場合はyakusokuが実行されrejectで値が戻れれるためcatchTrainを経由せずにcatchが実行されます。


//const keep_promise = false
//const on_schedule = true
約束破ってごめん

//const keep_promise = false
//const on_schedule = false
約束破ってごめん

このようにPromiseは連結できることがわかりました。