[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を使用してみました。
addPromisenew Promiseでプロミスオブジェクトを代入し、行いたい処理(ここではfor文)を記述します。処理が終わったら、resolve()メソッドを呼び出しPromiseの状態をresolvedに変化させます。

addPromiseresolvedに変化させた結果、then()メソッドが実行され、きちんとfor文の処理が終わった45が出力されるようになります。



Ex. rejected
rejectedresolvedと同じ形で使用できます。コードもあえて同じ処理にはしていますが、本来エラー用の処理を記述します。

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) を呼び出しています)

Promise.prototype.catch()より



参考

Promise - JavaScript | MDN

【ES6】 JavaScript初心者でもわかるPromise講座 - Qiita