Skip to content

Commit 3b81432

Browse files
authored
Allow Async Functions to be used in Server Components (#25479)
This is a temporary step until we allow Promises everywhere. Currently this serializes to a Lazy which can then be consumed in this same slot by the client.
1 parent 44e2ca3 commit 3b81432

File tree

2 files changed

+43
-15
lines changed

2 files changed

+43
-15
lines changed

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -350,25 +350,19 @@ describe('ReactFlightDOM', () => {
350350
}
351351

352352
function makeDelayedText() {
353-
let error, _resolve, _reject;
353+
let _resolve, _reject;
354354
let promise = new Promise((resolve, reject) => {
355355
_resolve = () => {
356356
promise = null;
357357
resolve();
358358
};
359359
_reject = e => {
360-
error = e;
361360
promise = null;
362361
reject(e);
363362
};
364363
});
365-
function DelayedText({children}, data) {
366-
if (promise) {
367-
throw promise;
368-
}
369-
if (error) {
370-
throw error;
371-
}
364+
async function DelayedText({children}) {
365+
await promise;
372366
return <Text>{children}</Text>;
373367
}
374368
return [DelayedText, _resolve, _reject];
@@ -469,7 +463,9 @@ describe('ReactFlightDOM', () => {
469463
resolveName();
470464
});
471465
// Advance time enough to trigger a nested fallback.
472-
jest.advanceTimersByTime(500);
466+
await act(async () => {
467+
jest.advanceTimersByTime(500);
468+
});
473469
expect(container.innerHTML).toBe(
474470
'<div>:name::avatar:</div>' +
475471
'<p>(loading sidebar)</p>' +
@@ -482,7 +478,8 @@ describe('ReactFlightDOM', () => {
482478
const theError = new Error('Game over');
483479
// Let's *fail* loading games.
484480
await act(async () => {
485-
rejectGames(theError);
481+
await rejectGames(theError);
482+
await 'the inner async function';
486483
});
487484
const expectedGamesValue = __DEV__
488485
? '<p>Game over + a dev digest</p>'
@@ -499,7 +496,8 @@ describe('ReactFlightDOM', () => {
499496

500497
// We can now show the sidebar.
501498
await act(async () => {
502-
resolvePhotos();
499+
await resolvePhotos();
500+
await 'the inner async function';
503501
});
504502
expect(container.innerHTML).toBe(
505503
'<div>:name::avatar:</div>' +
@@ -510,7 +508,8 @@ describe('ReactFlightDOM', () => {
510508

511509
// Show everything.
512510
await act(async () => {
513-
resolvePosts();
511+
await resolvePosts();
512+
await 'the inner async function';
514513
});
515514
expect(container.innerHTML).toBe(
516515
'<div>:name::avatar:</div>' +

packages/react-server/src/ReactFlightServer.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import type {
2121
ReactProviderType,
2222
ServerContextJSONValue,
2323
Wakeable,
24+
Thenable,
2425
} from 'shared/ReactTypes';
26+
import type {LazyComponent} from 'react/src/ReactLazy';
2527

2628
import {
2729
scheduleWork,
@@ -87,6 +89,7 @@ type ReactJSONValue =
8789

8890
export type ReactModel =
8991
| React$Element<any>
92+
| LazyComponent<any, any>
9093
| string
9194
| boolean
9295
| number
@@ -192,6 +195,25 @@ function createRootContext(
192195

193196
const POP = {};
194197

198+
function readThenable<T>(thenable: Thenable<T>): T {
199+
if (thenable.status === 'fulfilled') {
200+
return thenable.value;
201+
} else if (thenable.status === 'rejected') {
202+
throw thenable.reason;
203+
}
204+
throw thenable;
205+
}
206+
207+
function createLazyWrapperAroundWakeable(wakeable: Wakeable) {
208+
trackSuspendedWakeable(wakeable);
209+
const lazyType: LazyComponent<any, Thenable<any>> = {
210+
$$typeof: REACT_LAZY_TYPE,
211+
_payload: (wakeable: any),
212+
_init: readThenable,
213+
};
214+
return lazyType;
215+
}
216+
195217
function attemptResolveElement(
196218
type: any,
197219
key: null | React$Key,
@@ -214,7 +236,15 @@ function attemptResolveElement(
214236
}
215237
// This is a server-side component.
216238
prepareToUseHooksForComponent(prevThenableState);
217-
return type(props);
239+
const result = type(props);
240+
if (
241+
typeof result === 'object' &&
242+
result !== null &&
243+
typeof result.then === 'function'
244+
) {
245+
return createLazyWrapperAroundWakeable(result);
246+
}
247+
return result;
218248
} else if (typeof type === 'string') {
219249
// This is a host element. E.g. HTML.
220250
return [REACT_ELEMENT_TYPE, type, key, props];
@@ -636,7 +666,6 @@ export function resolveModelToJSON(
636666

637667
return serializeByRefID(newTask.id);
638668
} else {
639-
logRecoverableError(request, x);
640669
// Something errored. We'll still send everything we have up until this point.
641670
// We'll replace this element with a lazy reference that throws on the client
642671
// once it gets rendered.

0 commit comments

Comments
 (0)