[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
-});