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
Description
When a
catchbinding variable is accessed inside asetTimeoutcallback (deferred closure), Hermes throwsReferenceError: Property 'error' doesn't existat runtime, despite the closure correctly capturing the binding.Environment
Reproduction
The issue occurs in production code with this pattern:
Standalone repro (does NOT reproduce — the issue may require Metro's module system or React Native's runtime):
The standalone repro works correctly. The issue only manifests inside a React Native app bundled with Metro, possibly related to:
useAtomCallback/asynccallbacks)__d(function(...) { ... }))Workaround
Hoisting the catch binding to a
letdeclared before thetry/catchblock:Stack trace
Notes
erroris referenced inside afunction() {}within thecatchblock20999:26) points to the JSTimerssetTimeoutrunner