Skip to content

Commit

Permalink
[Flight] Send server reference error chunks to the client
Browse files Browse the repository at this point in the history
Previously when a called server reference function was rejected, no
error chunk was emitted, and the request was not properly closed.
  • Loading branch information
unstubbable committed Mar 3, 2023
1 parent b72ed69 commit 9e1d958
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 4 deletions.
14 changes: 12 additions & 2 deletions fixtures/flight/src/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,21 @@
import * as React from 'react';

export default function Button({action, children}) {
const [isPending, setIsPending] = React.useState(false);

return (
<button
disabled={isPending}
onClick={async () => {
const result = await action();
console.log(result);
setIsPending(true);
try {
const result = await action();
console.log(result);
} catch (error) {
console.error(error);
} finally {
setIsPending(false);
}
}}>
{children}
</button>
Expand Down
11 changes: 9 additions & 2 deletions fixtures/flight/src/actions.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
'use server';

export async function like() {
console.log('Like');
return 'Liked';
return new Promise((resolve, reject) =>
setTimeout(
() =>
Math.random() > 0.5
? resolve('Liked')
: reject(new Error('Failed to like')),
500
)
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -850,4 +850,49 @@ describe('ReactFlightDOMBrowser', () => {
const result = await actionProxy('!');
expect(result).toBe('Hello World!');
});

it('propagates server reference errors to the client', async () => {
let actionProxy;

function Client({action}) {
actionProxy = action;
return 'Click Me';
}

async function send(text) {
return Promise.reject(new Error(`Error for ${text}`));
}

const ServerModule = serverExports({send});
const ClientRef = clientExports(Client);

const stream = ReactServerDOMWriter.renderToReadableStream(
<ClientRef action={ServerModule.send} />,
webpackMap,
);

const response = ReactServerDOMReader.createFromReadableStream(stream, {
async callServer(ref, args) {
const fn = requireServerRef(ref);
return ReactServerDOMReader.createFromReadableStream(
ReactServerDOMWriter.renderToReadableStream(fn.apply(null, args)),
);
},
});

function App() {
return use(response);
}

const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(async () => {
root.render(<App />);
});

const expectedError = new Error('Error for test');
spyOnDevAndProd(console, 'error').mockImplementation(() => {});
await expect(actionProxy('test')).rejects.toThrow(expectedError);
expect(console.error.mock.calls).toEqual([[expectedError]]);
});
});
2 changes: 2 additions & 0 deletions packages/react-server/src/ReactFlightServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ function serializeThenable(request: Request, thenable: Thenable<any>): number {
} else {
emitErrorChunkProd(request, newTask.id, digest);
}
newTask.status = ERRORED;
pingTask(request, newTask);
},
);

Expand Down

0 comments on commit 9e1d958

Please sign in to comment.