Skip to content

Conversation

@jasnell
Copy link
Collaborator

@jasnell jasnell commented Dec 27, 2025

This is a sketch an implementation of a jsg::DeferredPromise<T>. Sketch impl Impl was generated by claude with heavy guidance (it took about three iterations to get it correct).

jsg::DeferredPromise<T> is a higher-performance alternative to jsg::Promise<T> for internal C++ code paths where promises frequently resolve synchronously. Specifically, unlike jsg::Promise<T> which always wrap a JS Promise with continuations that are always JS promise continuations, jsg::DeferredPromise<T> elides the JS promise and microtask queue entirely, implementing a compatible API model but invoking the continuations synchronously immediately when the DeferredPromise<T> is resolved/rejected. It can be bridged into jsg::Promise<T> when necessary.

Key benefits:

  • 20-40x faster than jsg::Promise in benchmarks (with the caveat that there are obvious behavior differences so it's not apples-to-apples... but that's also the point)
  • Defers V8 promise creation until explicitly needed via toJsPromise()
  • Executes continuations synchronously (no microtask queue overhead)
  • Trampolining prevents stack overflow on deep chains

The initial intent here is NOT to land this yet. This is part of the exploration of what performance improvements can be made to the streams implementation. The jsg::Promise<T> implementation adds a significant amount of overhead in complex streams scenarios.

The reason for the Deferred* in the name is that creation of the js Promise (via jsg::Promise<T>) can be deferred to when we actually need to cross the boundary out to JS.

  -------------------------------------------------------------------------------------
  Benchmark                                           Time             CPU   Iterations
  -------------------------------------------------------------------------------------
  Promise_ImmediateResolve_JsgPromise              9476 us         9456 us           79
  Promise_ImmediateResolve_Deferred                 358 us          358 us         1979

  Promise_ThenOnResolved_JsgPromise               37387 us        37346 us           19
  Promise_ThenOnResolved_Deferred                  1124 us         1124 us          618

  Promise_ChainOnResolved_JsgPromise             128730 us       128705 us            6
  Promise_ChainOnResolved_Deferred                 3373 us         3372 us          202

  Promise_PendingThenResolve_JsgPromise           38661 us        38651 us           18
  Promise_PendingThenResolve_Deferred              1882 us         1882 us          368

  Promise_ChainPendingThenResolve_JsgPromise     130912 us       130775 us            5
  Promise_ChainPendingThenResolve_Deferred         6256 us         6256 us          117

  Promise_ToJsPromise_AlreadyResolved             10283 us        10274 us           74
  Promise_ToJsPromise_Pending                     10983 us        10971 us           68

  Promise_FromJsPromise_WithChain                 47894 us        47884 us           15
  Promise_PureJsPromise_Chain                    129881 us       129795 us            6

  Promise_Void_JsgPromise                         32547 us        32533 us           23
  Promise_Void_Deferred                            1817 us         1817 us          388

  Promise_Rejection_JsgPromise                    37466 us        37433 us           19
  Promise_Rejection_Deferred                       4183 us         4170 us          182

  Promise_StreamSimulation_JsgPromise             37169 us        37136 us           19
  Promise_StreamSimulation_Deferred                1219 us         1219 us          579

  Promise_TryConsumeResolved                        377 us          377 us         1822

  Promise_DeepChain_Deferred                        128 us          128 us         5485
  Promise_DeepChain_JsgPromise                     3073 us         3070 us          235

  Performance Summary

  | Scenario                         | jsg::Promise | DeferredPromise | Speedup |
  |----------------------------------|--------------|-----------------|---------|
  | Immediate Resolve                | 9,476 µs     | 358 µs          | 26x     |
  | Then on Resolved                 | 37,387 µs    | 1,124 µs        | 33x     |
  | Chain on Resolved (10 callbacks) | 128,730 µs   | 3,373 µs        | 38x     |
  | Pending Then Resolve             | 38,661 µs    | 1,882 µs        | 21x     |
  | Chain Pending Then Resolve       | 130,912 µs   | 6,256 µs        | 21x     |
  | Void Promise                     | 32,547 µs    | 1,817 µs        | 18x     |
  | Rejection Handling               | 37,466 µs    | 4,183 µs        | 9x      |
  | Stream Simulation                | 37,169 µs    | 1,219 µs        | 30x     |
  | Deep Chain (100 callbacks)       | 3,073 µs     | 128 µs          | 24x     |

  The trampolining implementation keeps the deep chain test (100 callbacks) performing excellently at 128 µs vs 3,073 µs for jsg::Promise - a 24x speedup with no stack overflow risk.

This PR only adds the basic mechanism and tests. It does not update anything to use it yet. That'll come as a separate set of changes as the work on optimizing stream performance continues. See the tests and the doc comments for details on how the API is used. This intentionally does not change any existing stuff to use it, making it zero risk to land. We can continue to iterate on this safely without it being breaking.

@github-actions

This comment has been minimized.

@jasnell jasnell force-pushed the jasnell/deferredpromise-sketch branch from 0b8d68a to b2834ae Compare December 29, 2025 18:03
@jasnell jasnell marked this pull request as ready for review December 29, 2025 18:06
@jasnell jasnell requested review from a team as code owners December 29, 2025 18:06
@jasnell jasnell force-pushed the jasnell/deferredpromise-sketch branch from b2834ae to 18883ad Compare December 29, 2025 18:43
@jasnell jasnell force-pushed the jasnell/deferredpromise-sketch branch 3 times, most recently from 11a0960 to f1529bc Compare December 29, 2025 21:07
@jasnell jasnell force-pushed the jasnell/deferredpromise-sketch branch from f1529bc to 7489535 Compare December 30, 2025 20:16
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.

1 participant