JavaScriptのPromiseってなに?非同期処理をちょっとずつ理解しよう

こんにちは、さるまりんです。

前回の記事では、JavaScriptの同期処理と非同期処理について見てきました。

例えば、

console.log("こんにちは");

setTimeout(() => {
    console.log("あとで");
}, 1000);

console.log("さようなら");

と書くと、

こんにちは
さようなら
あとで

の順番で表示されるのでしたね。

setTimeout()の中に書いた処理は、その場ですぐに実行されるのではなく、少しあとで実行されます。

今回は、そんな非同期処理と一緒によく登場する、

Promise

について見ていきたいと思います。

Promiseってなに?

Promiseは、

あとで終わる処理の結果を表すオブジェクト

です。

いきなり「オブジェクト」と言われると、難しく感じるかもしれません。

今のところは、

今はまだ結果がないけれど、あとで成功したか、失敗したかが分かります

というものだと考えておきましょう。

Promiseという英単語には、

「約束」

という意味があります。

つまり、

今すぐには返せないけれど、終わったら結果を知らせますよ

という約束を表しているようなものなんです。

ただし、必ず成功するという約束ではありません。

処理が成功したのか、失敗したのか。そのどちらかを、あとで知らせてくれる仕組みです。

なぜPromiseがあるの?

非同期処理は、setTimeout()を使って書くこともできます。

例えば、さっきも出てきた、

setTimeout(() => {
    console.log("こんにちは");
}, 1000);

これは、1秒後に「こんにちは」と表示する処理です。

これくらいなら、それほど難しくありません。

でも、非同期処理が続いていくと、少し読みにくくなることがあります。

例えば、

setTimeout(() => {
    console.log("最初の処理");

    setTimeout(() => {
        console.log("次の処理");

        setTimeout(() => {
            console.log("最後の処理");
        }, 1000);
    }, 1000);
}, 1000);

処理の中に、また処理が入り、その中にさらに処理が入っています。

あー、パニックです。

どれがどう動くのか、分からなくなってきますよね。

何をしているのか追いかけるのが、大変になってきます。

Promiseを使うと、

「処理が終わったら、次に何をするか」

という流れを書きやすくなります。

Promiseを書いてみよう

まずは、簡単なPromiseを書いてみましょう。

const promise = new Promise((resolve) => {
    setTimeout(() => {
        resolve("こんにちは");
    }, 1000);
});

急にいろいろ出てきました。

一つずつ見てみます。

new Promise(...)

で、新しいPromiseを作っています。

その中にある、

resolve("こんにちは");

は、

処理が無事に終わりました
結果は「こんにちは」です

とPromiseへ伝えているようなものです。

このコードでは、1秒後に処理が終わり、「こんにちは」という結果が渡されます。

結果を受け取るにはthenを使います

作ったPromiseの結果は、then()を使って受け取れます。

promise.then((message) => {
    console.log(message);
});

全体をつなげると、こうなります。

const promise = new Promise((resolve) => {
    setTimeout(() => {
        resolve("こんにちは");
    }, 1000);
});

promise.then((message) => {
    console.log(message);
});

1秒後に、

こんにちは

と表示されます。

then()の中に書いた処理は、Promiseが無事に終わったあとで実行されます。

つまり、

結果が返ってきたら、その結果を使って次の処理をする

という書き方になっています。

resolveで渡した値を受け取っています

ここでもう一度、対応する部分を見てみましょう。

resolve("こんにちは");

で渡した値を、

(message) => {
    console.log(message);
}

messageで受け取っています。

変数名はmessageでなくても構いません。

例えば、

promise.then((result) => {
    console.log(result);
});

でも同じです。

Promiseが渡した結果を、then()の引数で受け取っているんですね。

Promiseには状態があります

Promiseには、主に3つの状態があります。

  • pending
  • fulfilled
  • rejected

英語のままだと、少し難しく見えますね。

意味を一つずつ見てみましょう。

pending

まだ結果を待っている状態です。

処理中

という感じです。

fulfilled

処理が無事に終わった状態です。

成功

と考えると分かりやすいと思います。

resolve()が呼ばれると、この状態になります。

rejected

処理に失敗した状態です。

失敗

という状態です。

Promiseは、

待っている
    ↓
成功する

または、

待っている
    ↓
失敗する

という流れで状態が変わります。

失敗するときはrejectを使います

Promiseでは、処理に失敗したことも表せます。

そのときに使うのが、

reject()

です。

例えば、

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject("処理に失敗しました");
    }, 1000);
});

このPromiseは、1秒後に失敗した状態になります。

ただし、このままでは失敗した理由を受け取っていません。

失敗したときの処理を書くには、catch()を使います。

catchで失敗を受け取ります

catch()は、Promiseが失敗したときに実行されます。

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject("処理に失敗しました");
    }, 1000);
});

promise.catch((error) => {
    console.log(error);
});

1秒後に、

処理に失敗しました

と表示されます。

成功したときはthen()

失敗したときはcatch()

という形です。

成功したらthen()で続き、失敗したらcatch()で捕まえる。

少しイメージしやすくありませんか?

thenとcatchをつなげて書きます

then()catch()は、つなげて書けます。

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("処理が終わりました");
    }, 1000);
});

promise
    .then((message) => {
        console.log(message);
    })
    .catch((error) => {
        console.log(error);
    });

この場合はresolve()が呼ばれているので、then()の処理が実行されます。

処理が終わりました

もし、

reject("処理に失敗しました");

が呼ばれた場合は、catch()の処理が実行されます。

成功するか失敗するかを変えてみましょう

ちょっとだけ動きを変えてみましょう。

const success = true;

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        if (success) {
            resolve("処理が終わりました");
        } else {
            reject("処理に失敗しました");
        }
    }, 1000);
});

promise
    .then((message) => {
        console.log(message);
    })
    .catch((error) => {
        console.log(error);
    });

successtrueなら、

処理が終わりました

と表示されます。

falseに変えると、

処理に失敗しました

と表示されます。

Promiseは、

あとで成功するかもしれない
失敗するかもしれない

という処理の結果を扱える仕組みなんですね。

Promiseを返す関数にしてみましょう

Promiseは、関数から返して使うことも多いです。

function getMessage() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve("こんにちは");
        }, 1000);
    });
}

この関数を呼び出すと、Promiseが返ってきます。

getMessage().then((message) => {
    console.log(message);
});

全体では、こうなります。

function getMessage() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve("こんにちは");
        }, 1000);
    });
}

getMessage().then((message) => {
    console.log(message);
});

getMessage()を呼び出した時点では、まだ「こんにちは」は返ってきません。

まずPromiseが返ってきて、そのPromiseの処理が終わったあとで、then()に結果が渡されます。

この、

関数を呼び出す
Promiseが返ってくる
あとで結果を受け取る

という形は、JavaScriptのコードでよく見かけます。

普通のreturnとは少し違う

普通の関数では、returnした値をその場で受け取れます。

function getMessage() {
    return "こんにちは";
}

const message = getMessage();

console.log(message);

この場合、messageにはすぐに「こんにちは」が入ります。

一方、Promiseを返す関数では、

function getMessage() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve("こんにちは");
        }, 1000);
    });
}

結果が分かるのは少しあとです。

そのため、

const message = getMessage();

と書いたとき、messageに入るのは「こんにちは」ではなく、Promiseです。

結果を受け取るには、

getMessage().then((message) => {
    console.log(message);
});

のようにthen()を使います。

ここは、Promiseを最初に見たときに少し迷いやすいところだと思います。

Promiseは「あとで知らせる」という約束

ここまでをまとめると、Promiseは、

今はまだ結果がないけれど、あとで成功したか、失敗したかを知らせます

という約束を表しています。

成功したときは、

resolve()

失敗したときは、

reject()

を使います。

そして、結果を受け取る側では、

then()

と、

catch()

を使います。

まずはこの形を覚えてみましょう

Promiseの基本的な形は、こんな感じです。

const promise = new Promise((resolve, reject) => {
    // 何か時間のかかる処理

    if (/* 成功したら */) {
        resolve("成功した結果");
    } else {
        reject("失敗した理由");
    }
});

promise
    .then((result) => {
        // 成功したとき
        console.log(result);
    })
    .catch((error) => {
        // 失敗したとき
        console.log(error);
    });

まずは、

  • Promiseは、あとで処理の結果が分かることを表す
  • 成功はresolve()
  • 失敗はreject()
  • 成功した結果はthen()
  • 失敗した理由はcatch()

というところまで見えてくれば十分だと思います。

おわりに

今回は、JavaScriptのPromiseについて見てきました。

Promiseは、

あとで終わる処理の結果を表すオブジェクト

です。

非同期処理では、その場ですぐに結果を受け取れないことがあります。

そんなとき、Promiseを使うと、

処理が終わったら何をするか
失敗したらどうするか

という流れを書けるようになります。

最初は少し記号が多く見えるかもしれません。

でも、

待っている
成功した
失敗した

という3つの状態で考えると、少し見えやすくなるのではないでしょうか。

次回は、このPromiseをさらに読みやすく書くための、

async / await

について見ていきたいと思います。

読んでくださってありがとうございました。

それではまた!