KAKUTA TECH BLOG

Blog Article

JavaScript Promise 初心者でもわかる非同期処理の基本

Promiseとは?

Promise(プロミス)は、非同期処理の結果を表すオブジェクトです。

簡単に言うと、「今は結果が分からないけれど、処理が終わったら成功したか失敗したかを知らせてくれるもの」です。

JavaScript では、API へのリクエストやファイルの読み込みなど、すぐには結果が返ってこない処理を扱うことがあります。このような非同期処理の結果を扱いやすくするために、Promise が使われます。

以前はコールバック関数を使って非同期処理をつなげることが多くありましたが、処理が増えるとコードの入れ子が深くなり、読みにくくなることがありました。

getUser(1, function (user) {
  getPosts(user.id, function (posts) {
    getComments(posts[0].id, function (comments) {
      console.log(comments)
    })
  })
})

このように処理が深くネストする状態は、コールバック地獄と呼ばれます。

Promise を使うと、非同期処理を .then() や .catch() でつなげられるため、このような複雑なネストを避けやすくなります。

Promise の3つの状態

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

状態

英語

意味

待機中

pending

非同期処理がまだ終わっていない

成功

fulfilled

非同期処理が成功した

失敗

rejected

非同期処理が失敗した

Promise は最初 pending の状態で作られます。その後、処理が成功すると fulfilled、失敗すると rejected に変わります。

一度 fulfilled または rejected になると、状態が元に戻ることはありません。

new Promise() で Promise を作る

Promise は、new Promise() を使って自分で作成できます。

const promise = new Promise((resolve, reject) => {
  const success = true

  if (success) {
    resolve("成功しました!")
  } else {
    reject(new Error("エラーが発生しました"))
  }
})

new Promise() に渡している関数には、resolve と reject という2つの関数が用意されます。

  • resolve():処理が成功したことを伝える
  • reject():処理が失敗したことを伝える

例えば、非同期処理が成功したら resolve() を呼び出します。

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("成功しました!")
  }, 1000)
})

この場合、Promise は最初 pending ですが、1秒後に resolve() が呼ばれると fulfilled になります。

resolve() に渡した "成功しました!" という値は、後から .then() で受け取れます。

.then() と .catch() で結果を受け取る

Promise の結果は、.then() と .catch() を使って処理できます。

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("成功しました!")
  }, 1000)
})

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

resolve() が呼ばれると、.then() が実行されます。

resolve("成功しました!")

↓

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

result には、resolve() に渡した "成功しました!" が入ります。

一方、reject() が呼ばれると .catch() が実行されます。

reject(new Error("エラーが発生しました"))

↓

.catch((error) => {
  console.error(error)
})

このように、resolve() と reject() で Promise の結果を決め、.then() と .catch() でその結果を受け取るという関係になっています。

Promise を使って非同期処理を作る

new Promise() は、既存の非同期処理を Promise として扱えるようにするときにも使えます。

例えば、1秒後に処理が完了する関数を作ります。

function asyncTask() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("タスク完了")
    }, 1000)
  })
}

この関数を実行すると Promise が返されるため、.then() を使えます。

asyncTask().then((message) => {
  console.log(message)
})

ここで重要なのは、asyncTask() が Promise を返していることです。

return new Promise(...)

と書いているため、asyncTask() の戻り値は Promise になります。

そのため、

asyncTask().then(...)

と書くことができます。

Promise を使った実践

Promise は、.then() をつなげることで複数の非同期処理を順番に実行できます。

function orderProduct(product) {
  return new Promise((resolve) => {
    console.log(`${product} を注文しました。`)

    setTimeout(() => {
      resolve(`${product} の支払いが完了しました。`)
    }, 2000)
  })
}

function shipProduct(paymentConfirmation) {
  return new Promise((resolve) => {
    console.log(paymentConfirmation)

    setTimeout(() => {
      resolve("商品が発送されました。")
    }, 1500)
  })
}

orderProduct("ノートPC")
  .then((paymentConfirmation) => {
    return shipProduct(paymentConfirmation)
  })
  .then((shippingStatus) => {
    console.log(shippingStatus)
  })

処理の流れは次のようになります。

  1. orderProduct() が Promise を返す
  2. 2秒後に resolve() が呼ばれる
  3. resolve() の値が最初の .then() に渡される
  4. shipProduct() が実行され、Promise を返す
  5. 1.5秒後に resolve() が呼ばれる
  6. その結果が次の .then() に渡される

つまり、

注文
 ↓
支払い完了
 ↓
発送完了

という順番で非同期処理をつなげることができます。

.then() の中では Promise を return する

ここで重要なのが return です。

.then((paymentConfirmation) => {
  return shipProduct(paymentConfirmation)
})

shipProduct() が Promise を返すため、その Promise を return しています。これによって、次の .then() が shipProduct() の完了を待てるようになります。

逆に return しないと、次の .then() は shipProduct() の完了を待ちません。

orderProduct("ノートPC")
  .then((paymentConfirmation) => {
    shipProduct(paymentConfirmation)
  })
  .then(() => {
    console.log("発送完了後に実行したい処理")
  })

この場合、shipProduct() の Promise を return していないため、次の .then() は発送処理の完了を待たずに実行されます。

そのため、.then() の中で次の非同期処理をつなげたい場合は、その Promise を return することが重要です。

fetch() も Promise を返す

Promise は、自分で new Promise() を書いて作るだけではありません。

例えば、fetch() は Promise を返す関数です。

fetch("https://jsonplaceholder.typicode.com/posts/1")
  .then((response) => response.json())
  .then((data) => {
    console.log("記事:", data)
  })
  .catch((error) => {
    console.error("エラー:", error)
  })

fetch() が Promise を返すため、new Promise() を自分で書かなくても .then() や .catch() を使えます。

const promise = fetch("https://example.com/data.json")

この promise には、fetch() が返した Promise が入っています。

そのため、以下のように結果を処理できます。

promise.then(...)

fetch() で resolve() や reject() を書かない理由

resolve() と reject() は、自分で new Promise() を作るときに、処理の成功・失敗を Promise に伝えるための関数です。fetch() はすでに Promise を返す仕組みになっているため、利用する側が resolve() や reject() を書く必要はありません。

ただし、fetch() は HTTP ステータスが 404 や 500 になっただけでは Promise を reject しません。ネットワークエラーなど、リクエスト自体が失敗した場合に reject されます。

そのため、HTTP ステータスも確認したい場合は、response.ok などを使って自分でエラーを発生させる必要があります。

fetch("https://example.com/data.json")
  .then((response) => {
    if (!response.ok) {
      throw new Error("データの取得に失敗しました")
    }

    return response.json()
  })
  .then((data) => {
    console.log(data)
  })
  .catch((error) => {
    console.error(error)
  })

Promise のポイント

Promise を理解するときは、次の関係を押さえておくと分かりやすくなります。

機能

役割

new Promise()

Promise を作る

resolve()

Promise を成功にする

reject()

Promise を失敗にする

.then()

成功した結果を処理する

.catch()

失敗した結果を処理する

return

次の .then() に Promise を渡す

Promise は、非同期処理の結果を管理し、その結果に応じて後続の処理をつなげるための仕組みです。

特に new Promise() を使う場合は、**「処理を実行する → resolve() / reject() で結果を決める → .then() / .catch() で結果を受け取る」**という流れを理解しておくことが重要です。