Skip to content

feat(napi): add ThreadsafeFunction::call_async_catch to handle errors in callback functions#3291

Merged
Brooooooklyn merged 1 commit into
napi-rs:mainfrom
sapphi-red:feat/tsfn-call-async-catch
May 13, 2026
Merged

feat(napi): add ThreadsafeFunction::call_async_catch to handle errors in callback functions#3291
Brooooooklyn merged 1 commit into
napi-rs:mainfrom
sapphi-red:feat/tsfn-call-async-catch

Conversation

@sapphi-red

@sapphi-red sapphi-red commented May 12, 2026

Copy link
Copy Markdown
Contributor

When a ThreadsafeFunction is parameterized with CalleeHandled = false and its JavaScript callback throws, calling tsfn.call_async(value).await does not surface the error to the awaiter and it terminates the host process with fatal runtime error: failed to initiate panic, error 5 (#2812). While this is an expected behavior, it is tedious to convert the callback to always receive a promise (rolldown/rolldown#9355) so that it doesn't cause a panic.

I did not change the behavior of call_async in this PR as you mentioned that it is an intentional behavior. Instead I added call_async_catch method.

Summary by CodeRabbit

  • New Features

    • Added a safer async API for threadsafe callbacks that captures JavaScript-thrown exceptions as returned errors instead of crashing the host.
  • Tests

    • Added async tests covering exception propagation and error preservation across the threadsafe callback boundary.
  • Examples

    • Exported new example helpers demonstrating the new async error-handling behavior.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7cdc6801-d5c0-4619-bbce-6e7dedad8a68

📥 Commits

Reviewing files that changed from the base of the PR and between b61561f and d9f062f.

⛔ Files ignored due to path filters (1)
  • examples/napi/__tests__/__snapshots__/values.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (8)
  • crates/napi/src/threadsafe_function.rs
  • examples/napi/__tests__/__snapshots__/values.spec.ts.md
  • examples/napi/__tests__/values.spec.ts
  • examples/napi/example.wasi-browser.js
  • examples/napi/example.wasi.cjs
  • examples/napi/index.cjs
  • examples/napi/index.d.cts
  • examples/napi/src/threadsafe_function.rs
✅ Files skipped from review due to trivial changes (1)
  • examples/napi/example.wasi.cjs
🚧 Files skipped from review as they are similar to previous changes (6)
  • examples/napi/example.wasi-browser.js
  • examples/napi/index.cjs
  • examples/napi/src/threadsafe_function.rs
  • examples/napi/tests/values.spec.ts
  • examples/napi/tests/snapshots/values.spec.ts.md
  • crates/napi/src/threadsafe_function.rs

📝 Walkthrough

Walkthrough

Adds a unified async API call_async_catch to ThreadsafeFunction for both CalleeHandled variants (wrapper for handled variant; oneshot + WithCallback implementation for unhandled), updates docs, and adds examples/tests and type/export updates demonstrating JS-throw propagation and recovery.

Changes

ThreadsafeFunction call_async_catch

Layer / File(s) Summary
Core call_async_catch implementation
crates/napi/src/threadsafe_function.rs
Adds call_async_catch for CalleeHandled=true (delegates to call_async) and for CalleeHandled=false (schedules TSFN call with a oneshot channel and WithCallback, returning Result<Return> and preserving JS-thrown exceptions as Err(napi::Error)); updates call_async docs to document crash behavior.
Example functions and test coverage
examples/napi/src/threadsafe_function.rs, examples/napi/__tests__/values.spec.ts, examples/napi/__tests__/__snapshots__/values.spec.ts.md, examples/napi/index.d.cts, examples/napi/index.cjs, examples/napi/example.wasi-browser.js, examples/napi/example.wasi.cjs
Adds three #[napi] async helpers exercising call_async_catch, three new async tests validating thrown-error propagation and recovery (including preservation of custom Error properties), snapshot/type declaration updates, and re-exports across CommonJS and WASI bridge files.

Sequence Diagram

sequenceDiagram
  participant Caller
  participant call_async_catch
  participant WithCallback
  participant OneshotReceiver
  participant JS_Callback
  Caller->>call_async_catch: enqueue call with value
  call_async_catch->>WithCallback: schedule TSFN call with oneshot sender
  WithCallback->>JS_Callback: invoke JS callback
  alt JS throws
    JS_Callback-->>WithCallback: throw
    WithCallback->>OneshotReceiver: send Err(PendingException)
  else JS returns
    JS_Callback-->>WithCallback: return value
    WithCallback->>OneshotReceiver: send Ok(Return)
  end
  call_async_catch->>OneshotReceiver: await result
  OneshotReceiver-->>call_async_catch: Result<Return>
  call_async_catch-->>Caller: Result<Return>
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • napi-rs/napi-rs#3188: Also adjusts ThreadsafeFunction error-handling paths; related to avoiding panics when constructing N-API errors during callback teardown.
  • napi-rs/napi-rs#3162: Modifies ThreadsafeFunction JS-callback error handling (populating napi::Error::cause), related to exception propagation semantics.

Poem

🐰 I nudged the async rope so tight,
I caught a throw in gentle light,
No host will crash,
Errors come back—
A rabbit's catch keeps code polite.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature addition: a new call_async_catch method to handle errors in callback functions for ThreadsafeFunction.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread crates/napi/src/threadsafe_function.rs
@Brooooooklyn Brooooooklyn force-pushed the feat/tsfn-call-async-catch branch from b61561f to d9f062f Compare May 13, 2026 06:38
@Brooooooklyn

Copy link
Copy Markdown
Member

@codex review

@Brooooooklyn Brooooooklyn changed the title feat: add ThreadsafeFunction::call_async_catch to handle errors in callback functions feat(napi): add ThreadsafeFunction::call_async_catch to handle errors in callback functions May 13, 2026
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@Brooooooklyn Brooooooklyn merged commit 3456eff into napi-rs:main May 13, 2026
70 checks passed
@github-actions github-actions Bot mentioned this pull request May 13, 2026
@sapphi-red sapphi-red deleted the feat/tsfn-call-async-catch branch May 14, 2026 02:13
graphite-app Bot pushed a commit to rolldown/rolldown that referenced this pull request May 14, 2026
Use `ThreadsafeFunction::call_async_catch` added in napi-rs/napi-rs#3291 so that we don't have to wrap values in promise on JS side.

refs #5931, #9355
IWANABETHATGUY pushed a commit to rolldown/rolldown that referenced this pull request May 18, 2026
Use `ThreadsafeFunction::call_async_catch` added in napi-rs/napi-rs#3291 so that we don't have to wrap values in promise on JS side.

refs #5931, #9355
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants