Skip to content

ReferenceError when accessing catch binding inside setTimeout callback #1969

Description

@exo-egor

Description

When a catch binding variable is accessed inside a setTimeout callback (deferred closure), Hermes throws ReferenceError: Property 'error' doesn't exist at runtime, despite the closure correctly capturing the binding.

Environment

  • Hermes via React Native 0.78
  • Metro bundler (dev mode, no minification)
  • iOS simulator

Reproduction

The issue occurs in production code with this pattern:

try {
  await someAsyncCall()
} catch (error) {
  console.error(error) // works fine
  
  setTimeout(function () {
    doSomething({ error }) // ReferenceError: Property 'error' doesn't exist
  }, 1000)
}

Standalone repro (does NOT reproduce — the issue may require Metro's module system or React Native's runtime):

let fn
try {
  throw new Error('boom')
} catch (error) {
  fn = () => error.message
}
print(fn()) // works fine with hermes CLI

The standalone repro works correctly. The issue only manifests inside a React Native app bundled with Metro, possibly related to:

  • Async function context (useAtomCallback / async callbacks)
  • Metro's module wrapper (__d(function(...) { ... }))
  • React Native Fast Refresh in dev mode rewiring module scopes

Workaround

Hoisting the catch binding to a let declared before the try/catch block:

let capturedError
try {
  await someAsyncCall()
} catch (error) {
  capturedError = error
  setTimeout(function () {
    doSomething({ error: capturedError }) // works
  }, 1000)
}

Stack trace

ERROR  uncaught error [ReferenceError: Property 'error' doesn't exist]
DEBUG  exodus:errorTracking:error ReferenceError: Property 'error' doesn't exist
    at anonymous (http://localhost:8081/index.bundle//<...>:1153678:13)
    at apply (native)
    at anonymous (http://localhost:8081/index.bundle//<...>:20999:26)

Notes

  • The bundled output (non-minified) shows the closure is correct — error is referenced inside a function() {} within the catch block
  • Babel did not transform the catch binding; the variable name is preserved
  • The second stack frame (20999:26) points to the JSTimers setTimeout runner

Metadata

Metadata

Assignees

No one assigned

    Labels

    need-reproPotentially valid, but blocked on missing test case to reproduce

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions