Skip to content

Commit 914319a

Browse files
authored
[Flight] Don't hang forever when prerendering a rejected promise (facebook#32953)
1 parent 3ef31d1 commit 914319a

File tree

3 files changed

+299
-17
lines changed

3 files changed

+299
-17
lines changed

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

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,4 +1354,285 @@ describe('ReactFlightDOMEdge', () => {
13541354
expect(error).not.toBe(null);
13551355
expect(error.message).toBe('Connection closed.');
13561356
});
1357+
1358+
// @gate experimental
1359+
it('should be able to handle a rejected promise in unstable_prerender', async () => {
1360+
const expectedError = new Error('Bam!');
1361+
const errors = [];
1362+
1363+
const {prelude} = await ReactServerDOMStaticServer.unstable_prerender(
1364+
Promise.reject(expectedError),
1365+
webpackMap,
1366+
{
1367+
onError(err) {
1368+
errors.push(err);
1369+
},
1370+
},
1371+
);
1372+
1373+
expect(errors).toEqual([expectedError]);
1374+
1375+
const response = ReactServerDOMClient.createFromReadableStream(prelude, {
1376+
serverConsumerManifest: {
1377+
moduleMap: {},
1378+
moduleLoading: {},
1379+
},
1380+
});
1381+
1382+
let error = null;
1383+
try {
1384+
await response;
1385+
} catch (x) {
1386+
error = x;
1387+
}
1388+
1389+
const expectedMessage = __DEV__
1390+
? expectedError.message
1391+
: 'An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.';
1392+
1393+
expect(error).not.toBe(null);
1394+
expect(error.message).toBe(expectedMessage);
1395+
});
1396+
1397+
// @gate experimental
1398+
it('should be able to handle an erroring async iterable in unstable_prerender', async () => {
1399+
const expectedError = new Error('Bam!');
1400+
const errors = [];
1401+
1402+
const {prelude} = await ReactServerDOMStaticServer.unstable_prerender(
1403+
{
1404+
async *[Symbol.asyncIterator]() {
1405+
await serverAct(() => {
1406+
throw expectedError;
1407+
});
1408+
},
1409+
},
1410+
webpackMap,
1411+
{
1412+
onError(err) {
1413+
errors.push(err);
1414+
},
1415+
},
1416+
);
1417+
1418+
expect(errors).toEqual([expectedError]);
1419+
1420+
const response = ReactServerDOMClient.createFromReadableStream(prelude, {
1421+
serverConsumerManifest: {
1422+
moduleMap: {},
1423+
moduleLoading: {},
1424+
},
1425+
});
1426+
1427+
let error = null;
1428+
try {
1429+
const result = await response;
1430+
const iterator = result[Symbol.asyncIterator]();
1431+
await iterator.next();
1432+
} catch (x) {
1433+
error = x;
1434+
}
1435+
1436+
const expectedMessage = __DEV__
1437+
? expectedError.message
1438+
: 'An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.';
1439+
1440+
expect(error).not.toBe(null);
1441+
expect(error.message).toBe(expectedMessage);
1442+
});
1443+
1444+
// @gate experimental
1445+
it('should be able to handle an erroring readable stream in unstable_prerender', async () => {
1446+
const expectedError = new Error('Bam!');
1447+
const errors = [];
1448+
1449+
const {prelude} = await ReactServerDOMStaticServer.unstable_prerender(
1450+
new ReadableStream({
1451+
async start(controller) {
1452+
await serverAct(() => {
1453+
setTimeout(() => {
1454+
controller.error(expectedError);
1455+
});
1456+
});
1457+
},
1458+
}),
1459+
webpackMap,
1460+
{
1461+
onError(err) {
1462+
errors.push(err);
1463+
},
1464+
},
1465+
);
1466+
1467+
expect(errors).toEqual([expectedError]);
1468+
1469+
const response = ReactServerDOMClient.createFromReadableStream(prelude, {
1470+
serverConsumerManifest: {
1471+
moduleMap: {},
1472+
moduleLoading: {},
1473+
},
1474+
});
1475+
1476+
let error = null;
1477+
try {
1478+
const stream = await response;
1479+
await stream.getReader().read();
1480+
} catch (x) {
1481+
error = x;
1482+
}
1483+
1484+
const expectedMessage = __DEV__
1485+
? expectedError.message
1486+
: 'An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.';
1487+
1488+
expect(error).not.toBe(null);
1489+
expect(error.message).toBe(expectedMessage);
1490+
});
1491+
1492+
// @gate experimental
1493+
it('can prerender an async iterable', async () => {
1494+
const errors = [];
1495+
1496+
const {prelude} = await ReactServerDOMStaticServer.unstable_prerender(
1497+
{
1498+
async *[Symbol.asyncIterator]() {
1499+
yield 'hello';
1500+
yield ' ';
1501+
yield 'world';
1502+
},
1503+
},
1504+
webpackMap,
1505+
{
1506+
onError(err) {
1507+
errors.push(err);
1508+
},
1509+
},
1510+
);
1511+
1512+
expect(errors).toEqual([]);
1513+
1514+
const response = ReactServerDOMClient.createFromReadableStream(prelude, {
1515+
serverConsumerManifest: {
1516+
moduleMap: {},
1517+
moduleLoading: {},
1518+
},
1519+
});
1520+
1521+
let text = '';
1522+
const result = await response;
1523+
const iterator = result[Symbol.asyncIterator]();
1524+
1525+
while (true) {
1526+
const {done, value} = await iterator.next();
1527+
if (done) {
1528+
break;
1529+
}
1530+
text += value;
1531+
}
1532+
1533+
expect(text).toBe('hello world');
1534+
});
1535+
1536+
// @gate experimental
1537+
it('can prerender a readable stream', async () => {
1538+
const errors = [];
1539+
1540+
const {prelude} = await ReactServerDOMStaticServer.unstable_prerender(
1541+
new ReadableStream({
1542+
start(controller) {
1543+
controller.enqueue('hello world');
1544+
controller.close();
1545+
},
1546+
}),
1547+
webpackMap,
1548+
{
1549+
onError(err) {
1550+
errors.push(err);
1551+
},
1552+
},
1553+
);
1554+
1555+
expect(errors).toEqual([]);
1556+
1557+
const response = ReactServerDOMClient.createFromReadableStream(prelude, {
1558+
serverConsumerManifest: {
1559+
moduleMap: {},
1560+
moduleLoading: {},
1561+
},
1562+
});
1563+
1564+
const stream = await response;
1565+
const result = await readResult(stream);
1566+
1567+
expect(result).toBe('hello world');
1568+
});
1569+
1570+
// @gate experimental
1571+
it('does not return a prerender prelude early when an error is emitted and there are still pending tasks', async () => {
1572+
let rejectPromise;
1573+
const rejectingPromise = new Promise(
1574+
(resolve, reject) => (rejectPromise = reject),
1575+
);
1576+
const expectedError = new Error('Boom!');
1577+
const errors = [];
1578+
1579+
const {prelude} = await ReactServerDOMStaticServer.unstable_prerender(
1580+
[
1581+
rejectingPromise,
1582+
{
1583+
async *[Symbol.asyncIterator]() {
1584+
yield 'hello';
1585+
yield ' ';
1586+
await serverAct(() => {
1587+
rejectPromise(expectedError);
1588+
});
1589+
yield 'world';
1590+
},
1591+
},
1592+
],
1593+
webpackMap,
1594+
{
1595+
onError(err) {
1596+
errors.push(err);
1597+
},
1598+
},
1599+
);
1600+
1601+
expect(errors).toEqual([expectedError]);
1602+
1603+
const response = ReactServerDOMClient.createFromReadableStream(prelude, {
1604+
serverConsumerManifest: {
1605+
moduleMap: {},
1606+
moduleLoading: {},
1607+
},
1608+
});
1609+
1610+
let text = '';
1611+
const [promise, iterable] = await response;
1612+
const iterator = iterable[Symbol.asyncIterator]();
1613+
1614+
while (true) {
1615+
const {done, value} = await iterator.next();
1616+
if (done) {
1617+
break;
1618+
}
1619+
text += value;
1620+
}
1621+
1622+
expect(text).toBe('hello world');
1623+
1624+
let error = null;
1625+
try {
1626+
await promise;
1627+
} catch (x) {
1628+
error = x;
1629+
}
1630+
1631+
const expectedMessage = __DEV__
1632+
? expectedError.message
1633+
: 'An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.';
1634+
1635+
expect(error).not.toBe(null);
1636+
expect(error.message).toBe(expectedMessage);
1637+
});
13571638
});

packages/react-server-dom-webpack/src/server/ReactFlightDOMServerEdge.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,6 @@ function prerender(
121121
const stream = new ReadableStream(
122122
{
123123
type: 'bytes',
124-
start: (controller): ?Promise<void> => {
125-
startWork(request);
126-
},
127124
pull: (controller): ?Promise<void> => {
128125
startFlowing(request, controller);
129126
},

0 commit comments

Comments
 (0)