Skip to content

Commit

Permalink
Allow Async Functions to be used in Server Components (#25479)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sebmarkbage authored Oct 14, 2022
1 parent 44e2ca3 commit 3b81432
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -350,25 +350,19 @@ describe('ReactFlightDOM', () => {
}

function makeDelayedText() {
let error, _resolve, _reject;
let _resolve, _reject;
let promise = new Promise((resolve, reject) => {
_resolve = () => {
promise = null;
resolve();
};
_reject = e => {
error = e;
promise = null;
reject(e);
};
});
function DelayedText({children}, data) {
if (promise) {
throw promise;
}
if (error) {
throw error;
}
async function DelayedText({children}) {
await promise;
return <Text>{children}</Text>;
}
return [DelayedText, _resolve, _reject];
Expand Down Expand Up @@ -469,7 +463,9 @@ describe('ReactFlightDOM', () => {
resolveName();
});
// Advance time enough to trigger a nested fallback.
jest.advanceTimersByTime(500);
await act(async () => {
jest.advanceTimersByTime(500);
});
expect(container.innerHTML).toBe(
'<div>:name::avatar:</div>' +
'<p>(loading sidebar)</p>' +
Expand All @@ -482,7 +478,8 @@ describe('ReactFlightDOM', () => {
const theError = new Error('Game over');
// Let's *fail* loading games.
await act(async () => {
rejectGames(theError);
await rejectGames(theError);
await 'the inner async function';
});
const expectedGamesValue = __DEV__
? '<p>Game over + a dev digest</p>'
Expand All @@ -499,7 +496,8 @@ describe('ReactFlightDOM', () => {

// We can now show the sidebar.
await act(async () => {
resolvePhotos();
await resolvePhotos();
await 'the inner async function';
});
expect(container.innerHTML).toBe(
'<div>:name::avatar:</div>' +
Expand All @@ -510,7 +508,8 @@ describe('ReactFlightDOM', () => {

// Show everything.
await act(async () => {
resolvePosts();
await resolvePosts();
await 'the inner async function';
});
expect(container.innerHTML).toBe(
'<div>:name::avatar:</div>' +
Expand Down
33 changes: 31 additions & 2 deletions packages/react-server/src/ReactFlightServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import type {
ReactProviderType,
ServerContextJSONValue,
Wakeable,
Thenable,
} from 'shared/ReactTypes';
import type {LazyComponent} from 'react/src/ReactLazy';

import {
scheduleWork,
Expand Down Expand Up @@ -87,6 +89,7 @@ type ReactJSONValue =

export type ReactModel =
| React$Element<any>
| LazyComponent<any, any>
| string
| boolean
| number
Expand Down Expand Up @@ -192,6 +195,25 @@ function createRootContext(

const POP = {};

function readThenable<T>(thenable: Thenable<T>): T {
if (thenable.status === 'fulfilled') {
return thenable.value;
} else if (thenable.status === 'rejected') {
throw thenable.reason;
}
throw thenable;
}

function createLazyWrapperAroundWakeable(wakeable: Wakeable) {
trackSuspendedWakeable(wakeable);
const lazyType: LazyComponent<any, Thenable<any>> = {
$$typeof: REACT_LAZY_TYPE,
_payload: (wakeable: any),
_init: readThenable,
};
return lazyType;
}

function attemptResolveElement(
type: any,
key: null | React$Key,
Expand All @@ -214,7 +236,15 @@ function attemptResolveElement(
}
// This is a server-side component.
prepareToUseHooksForComponent(prevThenableState);
return type(props);
const result = type(props);
if (
typeof result === 'object' &&
result !== null &&
typeof result.then === 'function'
) {
return createLazyWrapperAroundWakeable(result);
}
return result;
} else if (typeof type === 'string') {
// This is a host element. E.g. HTML.
return [REACT_ELEMENT_TYPE, type, key, props];
Expand Down Expand Up @@ -636,7 +666,6 @@ export function resolveModelToJSON(

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

0 comments on commit 3b81432

Please sign in to comment.