@samitouri / QOS-React-2 / commits / 3b814327e2

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.

Sebastian Markbåge committed Oct 14, 2022 at 15:09 UTC 3b814327e2390e6f3dfd4c855d849662ddd52b2a
2 files changed +43 -15
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js
+12 -13
@@ -350,25 +350,19 @@ describe('ReactFlightDOM', () => {
350 }
351
352 function makeDelayedText() {
353 - let error, _resolve, _reject;
353 + let _resolve, _reject;
354 let promise = new Promise((resolve, reject) => {
355 _resolve = () => {
356 promise = null;
357 resolve();
358 };
359 _reject = e => {
360 - error = e;
360 promise = null;
361 reject(e);
362 };
363 });
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;
366 return <Text>{children}</Text>;
367 }
368 return [DelayedText, _resolve, _reject];
@@ -469,7 +463,9 @@ describe('ReactFlightDOM', () => {
463 resolveName();
464 });
465 // Advance time enough to trigger a nested fallback.
472 - jest.advanceTimersByTime(500);
466 + await act(async () => {
467 + jest.advanceTimersByTime(500);
468 + });
469 expect(container.innerHTML).toBe(
470 '<div>:name::avatar:</div>' +
471 '<p>(loading sidebar)</p>' +
@@ -482,7 +478,8 @@ describe('ReactFlightDOM', () => {
478 const theError = new Error('Game over');
479 // Let's *fail* loading games.
480 await act(async () => {
485 - rejectGames(theError);
481 + await rejectGames(theError);
482 + await 'the inner async function';
483 });
484 const expectedGamesValue = __DEV__
485 ? '<p>Game over + a dev digest</p>'
@@ -499,7 +496,8 @@ describe('ReactFlightDOM', () => {
496
497 // We can now show the sidebar.
498 await act(async () => {
502 - resolvePhotos();
499 + await resolvePhotos();
500 + await 'the inner async function';
501 });
502 expect(container.innerHTML).toBe(
503 '<div>:name::avatar:</div>' +
@@ -510,7 +508,8 @@ describe('ReactFlightDOM', () => {
508
509 // Show everything.
510 await act(async () => {
513 - resolvePosts();
511 + await resolvePosts();
512 + await 'the inner async function';
513 });
514 expect(container.innerHTML).toBe(
515 '<div>:name::avatar:</div>' +
packages/react-server/src/ReactFlightServer.js
+31 -2
@@ -21,7 +21,9 @@ import type {
21 ReactProviderType,
22 ServerContextJSONValue,
23 Wakeable,
24 + Thenable,
25 } from 'shared/ReactTypes';
26 +import type {LazyComponent} from 'react/src/ReactLazy';
27
28 import {
29 scheduleWork,
@@ -87,6 +89,7 @@ type ReactJSONValue =
89
90 export type ReactModel =
91 | React$Element<any>
92 + | LazyComponent<any, any>
93 | string
94 | boolean
95 | number
@@ -192,6 +195,25 @@ function createRootContext(
195
196 const POP = {};
197
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 +
217 function attemptResolveElement(
218 type: any,
219 key: null | React$Key,
@@ -214,7 +236,15 @@ function attemptResolveElement(
236 }
237 // This is a server-side component.
238 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;
248 } else if (typeof type === 'string') {
249 // This is a host element. E.g. HTML.
250 return [REACT_ELEMENT_TYPE, type, key, props];
@@ -636,7 +666,6 @@ export function resolveModelToJSON(
666
667 return serializeByRefID(newTask.id);
668 } else {
639 - logRecoverableError(request, x);
669 // Something errored. We'll still send everything we have up until this point.
670 // We'll replace this element with a lazy reference that throws on the client
671 // once it gets rendered.