facebook / facebook/hermes

ReferenceError when accessing catch binding inside setTimeout callback

Open
#1,969 11 comments 0 reactions 0 assignees View on GitHub
need-repro
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.