@samitouri / QOS-React-2 / commits / c499adf8c8

[Flight] Allow Temporary References to be awaited (#34084)

Fixes #33534. `.then` method can be tested when you await a value that's not a Promise. For regular Client References we have a way to mark those as "async" and yield a reference to the unwrapped value in case it's a Promise on the Client. However, the realization is that we never serialize Promises as opaque when passed from the client to the server. If a Promise is passed, then it would've been deserialized as a Promise (while still registered as a temporary reference) and not one of these Proxy objects. Technically it could be a non-function value on the client which would be wrong but you're not supposed to dot into it in the first place. So we can just assume it's `undefined`.

Sebastian Markbåge committed Aug 2, 2025 at 18:44 UTC c499adf8c89bbfd884f4d3a58c4e510001383525
3 files changed +49 -37
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js
+44
@@ -438,6 +438,50 @@ describe('ReactFlightDOMReply', () => {
438 expect(response.obj).toBe(obj);
439 });
440
441 + it('can return an opaque object through an async function', async () => {
442 + function fn() {
443 + return 'this is a client function';
444 + }
445 +
446 + const args = [fn];
447 +
448 + const temporaryReferences =
449 + ReactServerDOMClient.createTemporaryReferenceSet();
450 + const body = await ReactServerDOMClient.encodeReply(args, {
451 + temporaryReferences,
452 + });
453 +
454 + const temporaryReferencesServer =
455 + ReactServerDOMServer.createTemporaryReferenceSet();
456 + const serverPayload = await ReactServerDOMServer.decodeReply(
457 + body,
458 + webpackServerMap,
459 + {temporaryReferences: temporaryReferencesServer},
460 + );
461 +
462 + async function action(arg) {
463 + return arg;
464 + }
465 +
466 + const stream = await serverAct(() =>
467 + ReactServerDOMServer.renderToReadableStream(
468 + {
469 + result: action.apply(null, serverPayload),
470 + },
471 + null,
472 + {temporaryReferences: temporaryReferencesServer},
473 + ),
474 + );
475 + const response = await ReactServerDOMClient.createFromReadableStream(
476 + stream,
477 + {
478 + temporaryReferences,
479 + },
480 + );
481 +
482 + expect(await response.result).toBe(fn);
483 + });
484 +
485 it('should supports streaming ReadableStream with objects', async () => {
486 let controller1;
487 let controller2;
packages/react-server/src/ReactFlightServerTemporaryReferences.js
+5 -1
@@ -70,8 +70,12 @@ const proxyHandlers = {
70 `Instead, you can export a Client Component wrapper ` +
71 `that itself renders a Client Context Provider.`,
72 );
73 - // Allow returning a temporary reference from an async function
73 case 'then':
74 + // Allow returning a temporary reference from an async function
75 + // Unlike regular Client References, a Promise would never have been serialized as
76 + // an opaque Temporary Reference, but instead would have been serialized as a
77 + // Promise on the server and so doesn't hit this path. So we can assume this wasn't
78 + // a Promise on the client.
79 return undefined;
80 }
81 throw new Error(
packages/react-server/src/__tests__/ReactFlightServerTemporaryReferences-test.js deleted
-36
@@ -1,36 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @emails react-core
8 - * @jest-environment node
9 - */
10 -
11 -'use strict';
12 -
13 -let ReactFlightServerTemporaryReferences;
14 -
15 -describe('ReactFlightServerTemporaryReferences', () => {
16 - beforeEach(() => {
17 - jest.resetModules();
18 - ReactFlightServerTemporaryReferences = require('react-server/src/ReactFlightServerTemporaryReferences');
19 - });
20 -
21 - it('can return a temporary reference from an async function', async () => {
22 - const temporaryReferenceSet =
23 - ReactFlightServerTemporaryReferences.createTemporaryReferenceSet();
24 - const temporaryReference =
25 - ReactFlightServerTemporaryReferences.createTemporaryReference(
26 - temporaryReferenceSet,
27 - 'test',
28 - );
29 -
30 - async function foo() {
31 - return temporaryReference;
32 - }
33 -
34 - await expect(foo()).resolves.toBe(temporaryReference);
35 - });
36 -});