[JavaScript] Promiseを完全に理解する
初めに
なんとなくで理解していたPromise
を完全に理解した状態を目指したいと思い、調べたことを備忘録として残しておきたいと思います。
前提
JavaScriptは非同期処理になります。処理を待たずに次の処理に移っていくため、例えば以下のようなコードだと
let count = 0 setTimeout(() => { for(let i = 0; i < 10; i++) { count += i } }, 3000) console.log(count) // => 実行結果 0
理想は、for文の処理を待ってから出力してほしいですが、 非同期処理なので、このような結果となります。
本題
Promise
前提のような処理結果を待って、その後に次に進みたいといった時に、Promiseは活躍します。
処理がうまくいったのか?それとも失敗したのか?
Promiseは処理の結果を返すようになります。このことを「プロミスを返す」と言います。
プロミスの状態
処理結果を返すために、プロミスは状態を扱います。状態は以下の3つのいずれかです。
pending・・・待機
new Promise()で作られたPromiseオブジェクトは、pendingというPromiseStatusで作られます。いわゆる初期状態で何も処理がされていません。resolved・・・履行(完了)
処理が終わり、成功した状態です。resolve()メソッドを使用して返します。
rejected・・・拒否(失敗)
処理が失敗した状態です。reject()メソッドを使用して返します。
初めはpending
ですが、メソッドを活用してpromiseの状態をresolved
, rejected
のどちらかに変化させます。
then()メソッド・catch()メソッド
- then()・・・resolvedになると実行するハンドラー
- catch()・・・rejectedになると実行するハンドラー
Ex. resolved
let count = 0 const addPromise = new Promise((resolve, reject) => { setTimeout(() => { for(let i = 0; i < 10; i++) { count += i } resolve() }, 3000) }) addPromise.then(() => { console.log(count) }) // => 実行結果 45
冒頭に挙げたコードに、Promiseを使用してみました。
addPromise
にnew Promise
でプロミスオブジェクトを代入し、行いたい処理(ここではfor文)を記述します。処理が終わったら、resolve()
メソッドを呼び出しPromiseの状態をresolved
に変化させます。
addPromise
をresolved
に変化させた結果、then()
メソッドが実行され、きちんとfor文の処理が終わった45
が出力されるようになります。
Ex. rejected
rejected
もresolved
と同じ形で使用できます。コードもあえて同じ処理にはしていますが、本来エラー用の処理を記述します。
let count = 0 const addPromise = new Promise((resolve, reject) => { setTimeout(() => { for(let i=0; i < 10; i++) { count += i } reject() }, 3000) }) addPromise.catch(() => { console.log(count) }) // => 実行結果 45
処理結果を返したい
また処理結果を返したいときは、resolve()メソッド
に値を渡すことで返すことができます。reject()メソッド
も同様の形で使用できます。
const addPromise = new Promise((resolve) => { setTimeout(() => { let count = 0 for(let i=0; i < 10; i++) { count += i } resolve(count) }, 3000) }) addPromise.then((count) => { console.log(count) }) // => 実行結果 45
繋げる
以下のような形でthen()メソッド
、catch()メソッド
をピリオドを用いて繋げて書くこともできます。
const addPromise = new Promise((resolve, reject) => { setTimeout(() => { let count = 0 for(let i=0; i < 10; i++) { count += i } resolve(count) }, 3000) }).then((count) => { console.log(count) }).catch(() => { console.log('error') }) // => 実行結果 45
Promise.all()メソッド
複数のpromiseオブジェクトを配列で渡し、全てのPromiseからの処理結果を待ちます
const promise1 = new Promise((resolve, reject) => { resolve(10) }) const promise2 = new Promise((resolve, reject) => { setTimeout(() => { resolve(20) }, 1000) }) const promise3 = new Promise((resolve, reject) => { setTimeout(() => { resolve(30) }, 2000) }) Promise.all([promise1, promise2, promise3]).then((values) => { console.log(values) }) // => 実行結果 [ 10, 20, 30 ]
補足
then()
メソッド
本題にてthen()メソッド内では、resolvedになると実行するハンドラーと書きましたが、rejectedの時にも処理はできます。厳密には、thenメソッドの中の処理が成功時は、onFulfilled
、失敗時はonRejected
がそれぞれ呼び出されるようになっています。
catch()メソッド
はこのonRejectedメソッド
を呼び出しているのだそうです。
個人的には、then()はresolved, catch()はrejectedという認識だけで、問題ないかと思います。
catch() メソッドは Promise を返しますが、拒否された場合のみ扱います。 Promise.prototype.then(undefined, onRejected) の呼び出しと同じ動作をします(実際、 obj.catch(onRejected) の呼び出しは内部的に obj.then(undefined, onRejected) を呼び出しています)