ReferenceError when accessing catch binding inside setTimeout callback
- Dominant language
- JavaScript
- Stars
- 11.3k
- Forks
- 859
- Avg merge
- 1h 30m
- Merged PRs (30d)
- 3
Description
## 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:
```javascript
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):
```javascript
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:
```javascript
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
Contributor guide
Research direction
Start by reproducing the failure in React Native 0.78 with Metro in dev mode, then compare it with the working standalone Hermes example. Inspect the Metro module wrapper and the JSTimers setTimeout runner referenced in the stack trace. Done means identifying a minimal reproduction and verifying that the deferred callback can access the catch binding without a ReferenceError.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100