こんにちは、さるまりんです。
前回の記事では、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);
});
successがtrueなら、
処理が終わりました
と表示されます。
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
について見ていきたいと思います。
読んでくださってありがとうございました。
それではまた!