Back

Uncaught (in promise) TypeErrorの対処方法

Uncaught (in promise) TypeErrorの対処方法

フロントエンドアプリケーションをデバッグしていると、コンソールに次のようなメッセージが表示されます:Uncaught (in promise) TypeError: Cannot read property 'type' of undefined。このエラーメッセージには、開発者がしばしば混同する2つの異なる問題が含まれています。JavaScriptのPromiseエラーハンドリングを適切に行うには、両方を理解することが不可欠です。

本記事では、このエラーが実際に何を意味するのか、なぜ表示されるのか、そしてブラウザ環境で未処理のPromise拒否を正しく処理する方法について説明します。

重要なポイント

  • 「Uncaught (in promise) TypeError」メッセージは、2つの別々の問題を示しています:ランタイムTypeErrorと、Promise拒否ハンドラの欠落です。
  • 一般的な原因には、浮遊Promise(awaitの欠落)、.catch()ハンドラの付け忘れ、undefinedまたはnullのプロパティへのアクセスがあります。
  • await式をtry/catchでラップするか、すべてのPromiseチェーンの最後に.catch()を付けて、未処理の拒否を防ぎます。
  • オプショナルチェーン(?.)とNull合体演算子(??)を使用して、未定義プロパティからのTypeErrorを防ぎます。
  • unhandledrejectionイベントは、ローカルエラーハンドリングの代替ではなく、監視とテレメトリのために使用してください。

‘Uncaught (in promise) TypeError’が実際に意味すること

このエラーメッセージは2つの別々の問題を伝えています:

  1. TypeError: ランタイムエラーが発生しました—通常はundefinedまたはnullのプロパティにアクセスしたことによるものです。
  2. Uncaught (in promise): このエラーを生成したPromiseに拒否ハンドラが付けられていませんでした。

ブラウザがこれらを区別するのは、Promise拒否が同期エラーとは異なる動作をするためです。同期的なTypeErrorが発生すると、実行は即座に停止します。同じエラーがPromise内で発生すると、拒否はPromiseチェーンを通じて伝播します。.catch()またはtry/catchがそれを処理しない場合、ブラウザは未処理のPromise拒否としてログに記録します。

Promiseの動作とエラー伝播に関する詳細なリファレンスについては、MDNドキュメントのPromisesを参照してください。

フロントエンドコードにおける一般的な根本原因

非同期関数内でスローされるエラー

async関数内の例外は、返されるPromiseを自動的に拒否します:

async function fetchUserData(userId) {
  const response = await fetch(`/api/users/${userId}`)
  const data = await response.json()
  return data.profile.name // profileがundefinedの場合TypeError
}

fetchUserData(123) // 浮遊Promise—ハンドラが付けられていない

awaitキーワードの欠落

awaitを忘れると、独立して実行される浮遊Promiseが作成されます:

async function processData() {
  fetchUserData(123) // awaitが欠落—拒否が未処理のまま
  console.log('Done')
}

.catch()ハンドラの付け忘れ

末尾の.catch()ブロックがないPromiseチェーンは、拒否を未処理のままにします:

fetch('/api/data')
  .then(res => res.json())
  .then(data => data.items[0].name) // .catch()なし—TypeErrorが伝播

遅延ハンドラのアタッチとマイクロタスクのタイミング

ブラウザは、現在のマイクロタスクチェックポイントの後に拒否が処理されたかどうかを判断します。その時点で拒否にハンドラがない場合、ブラウザはunhandledrejectionイベントを発行し、コンソール警告をログに記録する可能性があります。その直後にハンドラを付けても、開発ビルドでは警告がトリガーされることがあります:

const promise = Promise.reject(new Error('Failed'))

promise.catch(err => console.log('Handled'))

このタイミングの微妙な違いは、適切に処理された拒否が開発中に未処理として表示される理由を説明しています。

適切なAsync/Awaitエラーハンドリングパターン

awaitの周りのローカルtry/catch

最も信頼性の高いパターンは、await式をtry/catchでラップすることです:

async function fetchPokemon(id) {
  try {
    const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`)
    const data = await response.json()
    return {
      name: data.name,
      type: data.types[0]?.type.name,
      type2: data.types[1]?.type.name ?? null
    }
  } catch (error) {
    console.error('Fetch failed:', error.message)
    return null
  }
}

ハンドラを付けたPromiseの返却

非同期関数を呼び出す際は、常に返されるPromiseを処理してください:

// オプション1: try/catchでawait
await fetchPokemon(25)

// オプション2: 呼び出しに.catch()
fetchPokemon(25).catch(handleError)

.catch()の適切なチェーン

Promiseチェーンの最後に.catch()を配置して、チェーン内の任意の拒否をキャッチします:

Promise.all(urls.map(url => fetch(url).then(r => r.json())))
  .then(results => processResults(results))
  .catch(error => showErrorUI(error))

監視のためのunhandledrejectionイベントの使用

ブラウザは、グローバルエラー監視のためにunhandledrejectionイベントを提供しています—主要なエラーハンドリングメカニズムとしてではありません:

window.addEventListener('unhandledrejection', event => {
  // エラー監視サービスにログ
  errorTracker.capture(event.reason)

  // オプションでデフォルトのコンソールエラーを防ぐ
  event.preventDefault()
})

対となるrejectionhandledイベントは、以前に未処理だった拒否が後でハンドラを受け取ったときに発火します。これらのイベントはテレメトリや、ローカルハンドリングをすり抜けたエラーをキャッチするのに便利ですが、適切なtry/catch.catch()パターンの代わりにすべきではありません。

まとめ

「Uncaught (in promise) TypeError」メッセージは、ランタイムエラーとエラーハンドリングの欠落の両方を示しています。問題の両面に対処してください:オプショナルチェーン(?.)とNull合体演算子(??)を使用して未定義プロパティからのTypeErrorを防ぎ、非同期操作をtry/catchでラップするか、すべてのPromiseチェーンに.catch()ハンドラを付けます。unhandledrejectionイベントは制御フローではなく監視のために使用してください。これらのパターンにより、フロントエンドコードの堅牢性が保たれ、コンソールがクリーンに保たれます。

よくある質問

Uncaught TypeErrorは、実行を即座に停止する同期エラーです。Uncaught (in promise) TypeErrorは同じ種類のランタイムエラーですが、拒否ハンドラがないPromise内で発生しました。ブラウザは、非同期Promiseチェーンで拒否が未処理のままであることを示すために「in promise」ラベルを追加します。

はい。Promiseチェーンの最後にある単一の.catch()は、先行する任意の.then()コールバックで発生した任意の拒否をキャッチします。拒否は.catch()ハンドラを見つけるまでチェーンを伝播します。存在しない場合、ブラウザは未処理のPromise拒否として報告します。

.then()コールバック内のtry/catchブロックは、そのコールバック内でスローされた同期エラーをキャッチできます。ただし、それらのPromiseがasync関数内でawaitされない限り、Promise拒否をキャッチすることはできません。.then()チェーンでの拒否を処理するには、チェーンの最後に.catch()を付けてください。try/catchは主にasync/awaitパターンのために使用してください。

いいえ。unhandledrejectionイベントは、ローカルエラーハンドリングをすり抜けたPromise拒否をキャッチするためのセーフティネットです。ログと監視に最適です。常にtry/catchまたは.catch()でローカルにエラーを処理し、グローバルイベントリスナーは診断のためのフォールバックとして扱ってください。

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay