今日は、新米エンジニアにとっての大きな壁である、Promiseをとりあげます。エンジニアの採用面接でもよく聞かれるトピックです。JavaScriptを扱うならば、早めに苦手意識を克服しておきましょう。シンプルなコードで見れば、「Promiseという何か複雑な響き…」よりも、ずっとシンプルなので、ビビらずにいきましょう!
Contents
Promiseとは何か?
まずは、Promiseとは何か。言葉で説明します。
Promise とは、オブジェクトであり、非同期処理の結果を返します。非同期処理ですので、未来に帰ってくる結果です(値が返ってくるまでに時間がかかるイメージです。)。また、結果とは、処理が、成功したとか失敗したとか、はたまた、成功し返ってきた値などをさします。
Promiseは以下の3つの状態を持ちます。
- pending: 初期状態。まだ、処理が完了していない状態。
- fullfilled: 処理が成功して完了した。
- rejected: 処理が失敗した。
言葉である程度説明できることは、面接などでは大切ですので、以下のコード例を理解した後に、もう一度読んでみてください。
簡単なPromiseを自分で作り、.then()でPromiseを処理するパターン
この例では、自分で簡単なPromiseを定義し、.then()メソッドを利用して、処理しています。コード内のコメントで説明します。
const customMadePromise = new Promise((resolve, reject) => {
// aは常にtrueなので、このPromiseの処理は常に成功します。
const a = true;
if (a) {
// 処理が成功したら、'Sucess'という文字列を返すと定義する。
resolve('Sucess');
} else {
// 処理が失敗したら、'Failed'という文字列を返すと定義する。
reject('Failed');
}
})
// Promiseを処理。
customMadePromise.then(res => {
// Promiseが成功した時に呼ばれる関数ブロック
console.log(res); //"Sucess"。
}).catch(error => {
// Promiseが失敗した時に呼ばれる関数ブロック
console.log(error)
})
.then()のメソッドチェーン
.then()は、以下のコード例のように繋げて書くこともできます。
then()
が返すのもPromiseオブジェクトであるので、チェーンすることが可能です。then()
の中でreturn <値>
することは、resolve(<値>)
と等しくなります。一方で、何もreturnしないときは、resolve()
となるので、以下の例のようにundefinedとなります。
const customMadePromise = new Promise((resolve, reject) => {
// aは常にtrueなので、このPromiseの処理は常に成功します。
const a = true;
if (a) {
// 処理が成功したら、'Sucess'という文字列を返すと定義する。
resolve('Sucess');
} else {
// 処理が失敗したら、'Failed'という文字列を返すと定義する。
reject('Failed');
}
})
// Promiseを処理。
customMadePromise.then(
res => {
console.log(res) // "Sucess"
return "OK";
}
).then(
res => console.log(res) // "OK"。一つ前のチェーンで"OK"をリターンしているので。
).then(
res => console.log(res) // undefined。一つ前のチェーンで何も返していないので。
)
次は、この処理をrejectさせてみましょう。
customeMagePromiseはrejectするので、then()をスキップして、.catch()に入ります。
const customMadePromise = new Promise((resolve, reject) => {
// rejectさせます。
const a = false;
if (a) {
// 処理が成功したら、'Sucess'という文字列を返すと定義する。
resolve('Sucess');
} else {
// 処理が失敗したら、'Failed'という文字列を返すと定義する。
reject('Failed');
}
})
// Promiseを処理。
customMadePromise.then(
res => {
console.log(res)
return "OK";
}
).then(
res => console.log(res)
).then(
res => console.log(res)
).catch(err => console.log(err)) //"Failed"
Promise.resolve の使い方
Promise.resolve は、new Promiseのショートハンドです。書き方がすっきりするだけで仕組みは同じです。ちなみに、このように仕組みは同じで書き方が違うようなことを「シンタックスシュガー」といいます。
以下のコードは、両方とも「”Hello”」という文字列をresolveに渡しています。
Promise.resolve('Hello').then((res) => console.log(res)); // Hello
new Promise((resolve) => resolve('Hello')).then((res) => console.log(res)); // Hello
以上をまとめると、Promise.resolveとは「渡した値でFulfilledされるpromiseオブジェクトを返すメソッド」となります。見落としがちなポイントとして、上記のコードは、共にPromise Objectをリターンしますので、コンソールにでもコピペして確認してみてください。
Promise.reject の使い方
さっき説明した、Promise.resolveのrejectバージョンがあります。
同様に、以下の二つのコード例は基本的に同じことをしていますが、書き方が違うだけです。
new Promise((resolve, reject) => {
reject(new Error("エラー"));
}); // Uncaught (in promise) Error: エラー
Promise.reject(new Error("エラー")).catch((error) => {
console.error(error); // Error: エラー
});
Promise.finally()の使い方
ECMAScript 2018(ES9)から追加された、新しいメソッドに、Promise.finally()というものがあります。
Promise.finally()は、成功時と失敗時のどちらのケースでも呼び出したい処理を書くのに最適です。一点注意点をあげると、 .then メソッドと違い、.finallyメソッドのコールバック関数は引数を受け取りません。下記コード例ないのコメントでも説明しています。
function onFinally() {
// 成功、失敗どちらでも実行したい処理をかく
// 'final'というストリングをリターンしてみます。
return 'final'
}
// `Promise#finally` は新しいpromiseオブジェクトを返す
Promise.resolve(777)
.finally(onFinally)
.then((value) => {
// 呼び出し元のpromiseオブジェクトの状態をそのまま引き継ぐので `777` で resolveされている.
// then メソッドとは違い、.finally()メソッド内で値をリターンしてもPromise チェーンには影響なし。
// `final`はリターンされない。
console.log(value); // 777
});
Promise.all()の使い方
基本的には、先ほどの.then()と同じですが、Promise.allの場合には、渡されたpromiseオブジェクトの配列が全てresolveされた時に、 新規のpromiseオブジェクトはその値でresolveされます。新規のpromiseオブジェクトとは、Promise.all()の返り値のことです。
// まずは、3つのPromiseを定義します。ご覧の通り、全てresolveします。
const promise1 = new Promise((resolve, reject) => {
resolve('promise1 OK')
});
const promise2 = new Promise((resolve, reject) => {
resolve('promise2 OK')
});
const promise3 = new Promise((resolve, reject) => {
resolve('promise3 OK')
});
Promise.all([
promise1,
promise2,
promise3
]).then(res => console.log(res));// ["promise1 OK", "promise2 OK", "promise3 OK"]
次は、promise2をrejectされるように変更してみます。一つでもPromiseの値がrejectされた時点で、新たなpromiseオブジェクトはrejectされます。つまり、全ての処理が成功しないと、.then()のメソッドは実行されません。
const promise1 = new Promise((resolve, reject) => {
resolve('promise1 OK')
});
// こちらのPromiseをrejectに変更します。
const promise2 = new Promise((resolve, reject) => {
reject('promise2 Failed')
});
const promise3 = new Promise((resolve, reject) => {
resolve('promise3 OK')
});
Promise.all([
promise1,
promise2,
promise3
]).then(res => console.log(res)).catch(err => console.log(err))//"promise2 Failed"
Promise.race()の使い方
渡されたpromiseオブジェクトの配列のうち、 一番最初にresolve または rejectされたpromiseにより、 新たなpromiseオブジェクトはその値でresolve または rejectされます。
以下の例では、promise1が最初にresolveされたので、その時点で新たなpromiseオブジェクトは、”promise1 OK”をresolveします。
const promise1 = new Promise((resolve, reject) => {
resolve('promise1 OK')
});
const promise2 = new Promise((resolve, reject) => {
resolve('promise2 OK')
});
const promise3 = new Promise((resolve, reject) => {
resolve('promise3 OK')
});
// .all() を .race()に変更します。
Promise.race([
promise1,
promise2,
promise3
]).then(res => console.log(res)).catch(err => console.log(err))
// "promise1 OK"
まとめ
今日は、Promiseの基本を解説しました。同じ処理をやりたいのに、色々な書き方が存在するのが、初心者にとって混乱のポイントではないかと思います。やっている事は、そんなに複雑ではないので、今日のシンプルな例で一度整理してみると理解が進むと思います。