Skip to content

Add UnwrapIfPromiseAsync extension method for non-blocking promise resolution#2298

Merged
lahma merged 3 commits into
mainfrom
copilot/add-async-version-unwrap-if-promise
Mar 1, 2026
Merged

Add UnwrapIfPromiseAsync extension method for non-blocking promise resolution#2298
lahma merged 3 commits into
mainfrom
copilot/add-async-version-unwrap-if-promise

Conversation

Copilot AI commented Mar 1, 2026

Copy link
Copy Markdown
Contributor

UnwrapIfPromise blocks the calling thread while waiting for a JsPromise to settle, which causes thread pool exhaustion in async .NET environments. EvaluateAsync/InvokeAsync cover the common cases, but there's no async path when you already hold a JsValue that may be a promise (e.g., from Call(), GetValue(), etc.).

Changes

  • Engine.Async.cs: UnwrapResultAsync promoted from private to internal to allow delegation from extension methods.
  • JsValueExtensions.cs: New UnwrapIfPromiseAsync(this JsValue, CancellationToken = default) extension — delegates to promise.Engine.UnwrapResultAsync for promises, short-circuits with Task.FromResult for non-promise values.
  • README.md: Added a new "Asynchronous Execution" section documenting EvaluateAsync/ExecuteAsync/InvokeAsync and UnwrapIfPromiseAsync with code examples and guidance on when to use each.

Usage

// Blocking (old):
var result = engine.Invoke("fetchData", url).UnwrapIfPromise();

// Non-blocking (new):
var result = await engine.Invoke("fetchData", url).UnwrapIfPromiseAsync(cancellationToken);

Respects Options.Constraints.PromiseTimeout and cancellation, matching the behaviour of EvaluateAsync.

Original prompt

This section details on the original issue you should resolve

<issue_title>Feature Request: Add an Async Version of UnwrapIfPromise for JsValue</issue_title>
<issue_description>The engine currently provides UnwrapIfPromise, which allows synchronously waiting for a JsPromise and retrieving its result.

However, when the underlying JsPromise represents an I/O-bound asynchronous operation (for example, await httpClient.GetAsync(url)), UnwrapIfPromise blocks the calling thread while waiting for completion.

This blocking behavior can cause several issues:

  • Thread pool exhaustion in ASP.NET Core or other asynchronous environments
  • Inability to integrate naturally with existing async/await call chains
  • Reduced scalability and throughput in high-concurrency scenarios

To address this, would it be possible to introduce an asynchronous alternative, such as:

ValueTask<JsValue> UnwrapIfPromiseAsync(this JsValue value)

This method would asynchronously await the completion of a JsPromise instead of blocking the calling thread.

Benefits would include:

  • Preserving the async call chain end-to-end
  • Avoiding thread blocking
  • Better support for I/O-bound workloads and modern .NET async patterns

An async API would significantly improve integration in environments where non-blocking execution is critical.
</issue_description>

<agent_instructions>Check of there's real value here. Engine already has EvaluateAsync, will it suffice?</agent_instructions>

Comments on the Issue (you are @copilot in this section)


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

Co-authored-by: lahma <171892+lahma@users.noreply.github.com>
Copilot AI changed the title [WIP] Add async version of UnwrapIfPromise for JsValue Add UnwrapIfPromiseAsync extension method for non-blocking promise resolution Mar 1, 2026
Copilot finished work on behalf of lahma March 1, 2026 06:57
@lahma

lahma commented Mar 1, 2026

Copy link
Copy Markdown
Collaborator

@copilot update readme to contain both evaluateasync and this new feature.

…sync and UnwrapIfPromiseAsync

Co-authored-by: lahma <171892+lahma@users.noreply.github.com>

Copilot AI commented Mar 1, 2026

Copy link
Copy Markdown
Contributor Author

@copilot update readme to contain both evaluateasync and this new feature.

Added a new "Asynchronous Execution" section to the README in commit 4f86077. It covers EvaluateAsync/ExecuteAsync/InvokeAsync (with an HTTP fetch example, cancellation, and timeout notes) and UnwrapIfPromiseAsync (with examples for the "already have a JsValue" scenario), plus a note on when to prefer async over the synchronous UnwrapIfPromise.

Copilot finished work on behalf of lahma March 1, 2026 07:01
@lahma
lahma marked this pull request as ready for review March 1, 2026 07:04
@lahma

lahma commented Mar 1, 2026

Copy link
Copy Markdown
Collaborator

@copilot tests seem to hang

@lahma
lahma merged commit 6dc26c7 into main Mar 1, 2026
4 of 5 checks passed
@lahma
lahma deleted the copilot/add-async-version-unwrap-if-promise branch March 1, 2026 07:21
Copilot stopped work on behalf of lahma due to an error March 1, 2026 07:21
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.

Feature Request: Add an Async Version of UnwrapIfPromise for JsValue

2 participants