Commit 74568e8

Browse files
authored
[Flight] Transport AggregateErrors.errors (#36156)
1 parent 9627b5a commit 74568e8

10 files changed

Lines changed: 262 additions & 13 deletions

File tree

‎.eslintrc.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,7 @@ module.exports = {
566566
CallSite: 'readonly',
567567
ConsoleTask: 'readonly',// TOOD: Figure out what the official name of this will be.
568568
ReturnType: 'readonly',
569+
AggregateError: 'readonly',
569570
AnimationFrameID: 'readonly',
570571
WeakRef: 'readonly',
571572
// For Flow type annotation. Only `BigInt` is valid at runtime.

‎packages/internal-test-utils/ReactInternalTestUtils.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ ${diff(expectedLog, actualLog)}
122122

123123
functionaggregateErrors(errors: Array<mixed>): mixed{
124124
if(errors.length>1&&typeofAggregateError==='function'){
125-
// eslint-disable-next-line no-undef
126125
returnnewAggregateError(errors);
127126
}
128127
returnerrors[0];

‎packages/internal-test-utils/internalAct.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ async function waitForMicrotasks() {
3434

3535
functionaggregateErrors(errors: Array<mixed>): mixed{
3636
if(errors.length>1&&typeofAggregateError==='function'){
37-
// eslint-disable-next-line no-undef
3837
returnnewAggregateError(errors);
3938
}
4039
returnerrors[0];

‎packages/react-client/src/ReactFlightClient.js‎

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3525,7 +3525,8 @@ function resolveErrorDev(
35253525

35263526
leterror;
35273527
consterrorOptions=
3528-
'cause'inerrorInfo
3528+
// We don't serialize Error.cause in prod so we never need to deserialize
3529+
__DEV__&&'cause'inerrorInfo
35293530
? {
35303531
cause: reviveModel(
35313532
response,
@@ -3536,18 +3537,40 @@ function resolveErrorDev(
35363537
),
35373538
}
35383539
: undefined;
3540+
constisAggregateError=
3541+
typeofAggregateError!=='undefined'&&'errors'inerrorInfo;
3542+
constrevivedErrors=
3543+
// We don't serialize AggregateError.errors in prod so we never need to deserialize
3544+
__DEV__&&isAggregateError
3545+
? reviveModel(
3546+
response,
3547+
// $FlowFixMe[incompatible-cast]
3548+
(errorInfo.errors: JSONValue),
3549+
errorInfo,
3550+
'errors',
3551+
)
3552+
: null;
35393553
constcallStack=buildFakeCallStack(
35403554
response,
35413555
stack,
35423556
env,
35433557
false,
3544-
// $FlowFixMe[incompatible-use]
3545-
Error.bind(
3546-
null,
3547-
message||
3548-
'An error occurred in the Server Components render but no message was provided',
3549-
errorOptions,
3550-
),
3558+
isAggregateError
3559+
? // $FlowFixMe[incompatible-use]
3560+
AggregateError.bind(
3561+
null,
3562+
revivedErrors,
3563+
message||
3564+
'An error occurred in the Server Components render but no message was provided',
3565+
errorOptions,
3566+
)
3567+
: // $FlowFixMe[incompatible-use]
3568+
Error.bind(
3569+
null,
3570+
message||
3571+
'An error occurred in the Server Components render but no message was provided',
3572+
errorOptions,
3573+
),
35513574
);
35523575

35533576
letownerTask: null|ConsoleTask=null;

‎packages/react-client/src/__tests__/ReactFlight-test.js‎

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,204 @@ describe('ReactFlight', () => {
840840
}
841841
});
842842

843+
it('can transport AggregateError',async()=>{
844+
functionrenderError(error){
845+
if(!(errorinstanceofError)){
846+
return`${JSON.stringify(error)}`;
847+
}
848+
letresult=`
849+
is error: ${errorinstanceofAggregateError ? 'AggregateError' : 'Error'}
850+
name: ${error.name}
851+
message: ${error.message}
852+
stack: ${normalizeCodeLocInfo(error.stack).split('\n').slice(0,2).join('\n')}
853+
environmentName: ${error.environmentName}
854+
cause: ${'cause'inerror ? renderError(error.cause) : 'no cause'}`;
855+
if('errors'inerror){
856+
result+=`
857+
errors: [${error.errors.map(e=>renderError(e)).join(',\n')}]`;
858+
}
859+
returnresult;
860+
}
861+
functionComponentClient({error}){
862+
returnrenderError(error);
863+
}
864+
constComponent=clientReference(ComponentClient);
865+
866+
functionServerComponent(){
867+
consterror1=newTypeError('first error');
868+
consterror2=newRangeError('second error');
869+
consterror=newAggregateError([error1,error2],'aggregate');
870+
return<Componenterror={error}/>;
871+
}
872+
873+
consttransport=ReactNoopFlightServer.render(<ServerComponent/>,{
874+
onError(x){
875+
if(__DEV__){
876+
return'a dev digest';
877+
}
878+
return`digest("${x.message}")`;
879+
},
880+
});
881+
882+
awaitact(()=>{
883+
ReactNoop.render(ReactNoopFlightClient.read(transport));
884+
});
885+
886+
if(__DEV__){
887+
expect(ReactNoop).toMatchRenderedOutput(`
888+
is error: AggregateError
889+
name: AggregateError
890+
message: aggregate
891+
stack: AggregateError: aggregate
892+
in ServerComponent (at **)
893+
environmentName: Server
894+
cause: no cause
895+
errors: [
896+
is error: Error
897+
name: TypeError
898+
message: first error
899+
stack: TypeError: first error
900+
in ServerComponent (at **)
901+
environmentName: Server
902+
cause: no cause,
903+
904+
is error: Error
905+
name: RangeError
906+
message: second error
907+
stack: RangeError: second error
908+
in ServerComponent (at **)
909+
environmentName: Server
910+
cause: no cause]`);
911+
}else{
912+
expect(ReactNoop).toMatchRenderedOutput(`
913+
is error: Error
914+
name: Error
915+
message: 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.
916+
stack: Error: 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.
917+
environmentName: undefined
918+
cause: no cause`);
919+
}
920+
});
921+
922+
it('includes AggregateError.errors in thrown errors',async()=>{
923+
functionrenderError(error){
924+
if(!(errorinstanceofError)){
925+
return`${JSON.stringify(error)}`;
926+
}
927+
letresult=`
928+
is error: ${errorinstanceofAggregateError ? 'AggregateError' : 'Error'}
929+
name: ${error.name}
930+
message: ${error.message}
931+
stack: ${normalizeCodeLocInfo(error.stack).split('\n').slice(0,2).join('\n')}
932+
environmentName: ${error.environmentName}
933+
cause: ${'cause'inerror ? renderError(error.cause) : 'no cause'}`;
934+
if('errors'inerror){
935+
result+=`
936+
errors: [${error.errors.map(e=>renderError(e)).join(',\n')}]`;
937+
}
938+
returnresult;
939+
}
940+
941+
functionServerComponent(){
942+
consterror1=newTypeError('first error');
943+
consterror2=newRangeError('second error');
944+
consterror3=newError('third error');
945+
consterror4=newError('fourth error');
946+
consterror5=newError('fifth error');
947+
consterror6=newError('sixth error');
948+
consterror=newAggregateError(
949+
[error1,error2,error3,error4,error5,error6],
950+
'aggregate',
951+
);
952+
throwerror;
953+
}
954+
955+
consttransport=ReactNoopFlightServer.render(<ServerComponent/>,{
956+
onError(x){
957+
if(__DEV__){
958+
return'a dev digest';
959+
}
960+
return`digest("${x.message}")`;
961+
},
962+
});
963+
964+
leterror;
965+
try{
966+
awaitact(()=>{
967+
ReactNoop.render(ReactNoopFlightClient.read(transport));
968+
});
969+
}catch(x){
970+
error=x;
971+
}
972+
973+
if(__DEV__){
974+
expect(renderError(error)).toEqual(`
975+
is error: AggregateError
976+
name: AggregateError
977+
message: aggregate
978+
stack: AggregateError: aggregate
979+
in ServerComponent (at **)
980+
environmentName: Server
981+
cause: no cause
982+
errors: [
983+
is error: Error
984+
name: TypeError
985+
message: first error
986+
stack: TypeError: first error
987+
in ServerComponent (at **)
988+
environmentName: Server
989+
cause: no cause,
990+
991+
is error: Error
992+
name: RangeError
993+
message: second error
994+
stack: RangeError: second error
995+
in ServerComponent (at **)
996+
environmentName: Server
997+
cause: no cause,
998+
999+
is error: Error
1000+
name: Error
1001+
message: third error
1002+
stack: Error: third error
1003+
in ServerComponent (at **)
1004+
environmentName: Server
1005+
cause: no cause,
1006+
1007+
is error: Error
1008+
name: Error
1009+
message: fourth error
1010+
stack: Error: fourth error
1011+
in ServerComponent (at **)
1012+
environmentName: Server
1013+
cause: no cause,
1014+
1015+
is error: Error
1016+
name: Error
1017+
message: fifth error
1018+
stack: Error: fifth error
1019+
in ServerComponent (at **)
1020+
environmentName: Server
1021+
cause: no cause,
1022+
1023+
is error: Error
1024+
name: Error
1025+
message: sixth error
1026+
stack: Error: sixth error
1027+
in ServerComponent (at **)
1028+
environmentName: Server
1029+
cause: no cause]`);
1030+
}else{
1031+
expect(renderError(error)).toEqual(`
1032+
is error: Error
1033+
name: Error
1034+
message: 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.
1035+
stack: Error: 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.
1036+
environmentName: undefined
1037+
cause: no cause`);
1038+
}
1039+
});
1040+
8431041
it('can transport cyclic objects',async()=>{
8441042
functionComponentClient({prop}){
8451043
expect(prop.obj.obj.obj).toBe(prop.obj.obj);

‎packages/react-dom/src/__tests__/ReactDOMSelect-test.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1485,7 +1485,6 @@ describe('ReactDOMSelect', () => {
14851485
);
14861486
}),
14871487
).rejects.toThrowError(
1488-
// eslint-disable-next-line no-undef
14891488
newAggregateError([
14901489
newTypeError('prod message'),
14911490
newTypeError('prod message'),

‎packages/react-reconciler/src/__tests__/ReactFlushSync-test.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,6 @@ describe('ReactFlushSync', () => {
337337
expect(getVisibleChildren(container3)).toEqual('aww');
338338

339339
// Because there were multiple errors, React threw an AggregateError.
340-
// eslint-disable-next-line no-undef
341340
expect(error).toBeInstanceOf(AggregateError);
342341
expect(error.errors.length).toBe(2);
343342
expect(error.errors[0]).toBe(aahh);

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4169,6 +4169,14 @@ function serializeErrorValue(request: Request, error: Error): string {
41694169
constcauseId=outlineModel(request,cause);
41704170
errorInfo.cause=serializeByValueID(causeId);
41714171
}
4172+
if(
4173+
typeofAggregateError!=='undefined'&&
4174+
errorinstanceofAggregateError
4175+
){
4176+
consterrors: ReactClientValue=(error.errors: any);
4177+
consterrorsId=outlineModel(request,errors);
4178+
errorInfo.errors=serializeByValueID(errorsId);
4179+
}
41724180
constid=outlineModel(request,errorInfo);
41734181
return'$Z'+id.toString(16);
41744182
}else{
@@ -4211,6 +4219,15 @@ function serializeDebugErrorValue(
42114219
constcauseId=outlineDebugModel(request,counter,cause);
42124220
errorInfo.cause=serializeByValueID(causeId);
42134221
}
4222+
if(
4223+
typeofAggregateError!=='undefined'&&
4224+
errorinstanceofAggregateError
4225+
){
4226+
counter.objectLimit--;
4227+
consterrors: ReactClientValue=(error.errors: any);
4228+
consterrorsId=outlineDebugModel(request,counter,errors);
4229+
errorInfo.errors=serializeByValueID(errorsId);
4230+
}
42144231
constid=outlineDebugModel(
42154232
request,
42164233
{objectLimit: stack.length*2+1},
@@ -4240,6 +4257,7 @@ function emitErrorChunk(
42404257
letstack: ReactStackTrace;
42414258
letenv=(0,request.environmentName)();
42424259
letcauseReference: null|string=null;
4260+
leterrorsReference: null|string=null;
42434261
try{
42444262
if(errorinstanceofError){
42454263
name=error.name;
@@ -4259,6 +4277,16 @@ function emitErrorChunk(
42594277
: outlineModel(request,cause);
42604278
causeReference=serializeByValueID(causeId);
42614279
}
4280+
if(
4281+
typeofAggregateError!=='undefined'&&
4282+
errorinstanceofAggregateError
4283+
){
4284+
consterrors: ReactClientValue=(error.errors: any);
4285+
consterrorsId=debug
4286+
? outlineDebugModel(request,{objectLimit: 5},errors)
4287+
: outlineModel(request,errors);
4288+
errorsReference=serializeByValueID(errorsId);
4289+
}
42624290
}elseif(typeoferror==='object'&&error!==null){
42634291
message=describeObjectForErrorMessage(error);
42644292
stack=[];
@@ -4277,6 +4305,9 @@ function emitErrorChunk(
42774305
if(causeReference!==null){
42784306
(errorInfo: ReactErrorInfoDev).cause=causeReference;
42794307
}
4308+
if(errorsReference!==null){
4309+
(errorInfo: ReactErrorInfoDev).errors=errorsReference;
4310+
}
42804311
}else{
42814312
errorInfo ={digest};
42824313
}

‎packages/react/src/ReactAct.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ let didWarnNoAwaitAct = false;
2323

2424
functionaggregateErrors(errors: Array<mixed>): mixed{
2525
if(errors.length>1&&typeofAggregateError==='function'){
26-
// eslint-disable-next-line no-undef
2726
returnnewAggregateError(errors);
2827
}
2928
returnerrors[0];

‎packages/shared/ReactTypes.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ export type ReactErrorInfoDev = {
244244
+env: string,
245245
+owner?: null|string,
246246
cause?: JSONValue,
247+
errors?: JSONValue,
247248
};
248249

249250
export type ReactErrorInfo = ReactErrorInfoProd | ReactErrorInfoDev;

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Commit 74568e8

Browse files
authored
[Flight] Transport AggregateErrors.errors (#36156)
1 parent 9627b5a commit 74568e8

10 files changed

Lines changed: 262 additions & 13 deletions

File tree

‎.eslintrc.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,7 @@ module.exports = {
566566
CallSite: 'readonly',
567567
ConsoleTask: 'readonly',// TOOD: Figure out what the official name of this will be.
568568
ReturnType: 'readonly',
569+
AggregateError: 'readonly',
569570
AnimationFrameID: 'readonly',
570571
WeakRef: 'readonly',
571572
// For Flow type annotation. Only `BigInt` is valid at runtime.

‎packages/internal-test-utils/ReactInternalTestUtils.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ ${diff(expectedLog, actualLog)}
122122

123123
functionaggregateErrors(errors: Array<mixed>): mixed{
124124
if(errors.length>1&&typeofAggregateError==='function'){
125-
// eslint-disable-next-line no-undef
126125
returnnewAggregateError(errors);
127126
}
128127
returnerrors[0];

‎packages/internal-test-utils/internalAct.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ async function waitForMicrotasks() {
3434

3535
functionaggregateErrors(errors: Array<mixed>): mixed{
3636
if(errors.length>1&&typeofAggregateError==='function'){
37-
// eslint-disable-next-line no-undef
3837
returnnewAggregateError(errors);
3938
}
4039
returnerrors[0];

‎packages/react-client/src/ReactFlightClient.js‎

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3525,7 +3525,8 @@ function resolveErrorDev(
35253525

35263526
leterror;
35273527
consterrorOptions=
3528-
'cause'inerrorInfo
3528+
// We don't serialize Error.cause in prod so we never need to deserialize
3529+
__DEV__&&'cause'inerrorInfo
35293530
? {
35303531
cause: reviveModel(
35313532
response,
@@ -3536,18 +3537,40 @@ function resolveErrorDev(
35363537
),
35373538
}
35383539
: undefined;
3540+
constisAggregateError=
3541+
typeofAggregateError!=='undefined'&&'errors'inerrorInfo;
3542+
constrevivedErrors=
3543+
// We don't serialize AggregateError.errors in prod so we never need to deserialize
3544+
__DEV__&&isAggregateError
3545+
? reviveModel(
3546+
response,
3547+
// $FlowFixMe[incompatible-cast]
3548+
(errorInfo.errors: JSONValue),
3549+
errorInfo,
3550+
'errors',
3551+
)
3552+
: null;
35393553
constcallStack=buildFakeCallStack(
35403554
response,
35413555
stack,
35423556
env,
35433557
false,
3544-
// $FlowFixMe[incompatible-use]
3545-
Error.bind(
3546-
null,
3547-
message||
3548-
'An error occurred in the Server Components render but no message was provided',
3549-
errorOptions,
3550-
),
3558+
isAggregateError
3559+
? // $FlowFixMe[incompatible-use]
3560+
AggregateError.bind(
3561+
null,
3562+
revivedErrors,
3563+
message||
3564+
'An error occurred in the Server Components render but no message was provided',
3565+
errorOptions,
3566+
)
3567+
: // $FlowFixMe[incompatible-use]
3568+
Error.bind(
3569+
null,
3570+
message||
3571+
'An error occurred in the Server Components render but no message was provided',
3572+
errorOptions,
3573+
),
35513574
);
35523575

35533576
letownerTask: null|ConsoleTask=null;

‎packages/react-client/src/__tests__/ReactFlight-test.js‎

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,204 @@ describe('ReactFlight', () => {
840840
}
841841
});
842842

843+
it('can transport AggregateError',async()=>{
844+
functionrenderError(error){
845+
if(!(errorinstanceofError)){
846+
return`${JSON.stringify(error)}`;
847+
}
848+
letresult=`
849+
is error: ${errorinstanceofAggregateError ? 'AggregateError' : 'Error'}
850+
name: ${error.name}
851+
message: ${error.message}
852+
stack: ${normalizeCodeLocInfo(error.stack).split('\n').slice(0,2).join('\n')}
853+
environmentName: ${error.environmentName}
854+
cause: ${'cause'inerror ? renderError(error.cause) : 'no cause'}`;
855+
if('errors'inerror){
856+
result+=`
857+
errors: [${error.errors.map(e=>renderError(e)).join(',\n')}]`;
858+
}
859+
returnresult;
860+
}
861+
functionComponentClient({error}){
862+
returnrenderError(error);
863+
}
864+
constComponent=clientReference(ComponentClient);
865+
866+
functionServerComponent(){
867+
consterror1=newTypeError('first error');
868+
consterror2=newRangeError('second error');
869+
consterror=newAggregateError([error1,error2],'aggregate');
870+
return<Componenterror={error}/>;
871+
}
872+
873+
consttransport=ReactNoopFlightServer.render(<ServerComponent/>,{
874+
onError(x){
875+
if(__DEV__){
876+
return'a dev digest';
877+
}
878+
return`digest("${x.message}")`;
879+
},
880+
});
881+
882+
awaitact(()=>{
883+
ReactNoop.render(ReactNoopFlightClient.read(transport));
884+
});
885+
886+
if(__DEV__){
887+
expect(ReactNoop).toMatchRenderedOutput(`
888+
is error: AggregateError
889+
name: AggregateError
890+
message: aggregate
891+
stack: AggregateError: aggregate
892+
in ServerComponent (at **)
893+
environmentName: Server
894+
cause: no cause
895+
errors: [
896+
is error: Error
897+
name: TypeError
898+
message: first error
899+
stack: TypeError: first error
900+
in ServerComponent (at **)
901+
environmentName: Server
902+
cause: no cause,
903+
904+
is error: Error
905+
name: RangeError
906+
message: second error
907+
stack: RangeError: second error
908+
in ServerComponent (at **)
909+
environmentName: Server
910+
cause: no cause]`);
911+
}else{
912+
expect(ReactNoop).toMatchRenderedOutput(`
913+
is error: Error
914+
name: Error
915+
message: 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.
916+
stack: Error: 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.
917+
environmentName: undefined
918+
cause: no cause`);
919+
}
920+
});
921+
922+
it('includes AggregateError.errors in thrown errors',async()=>{
923+
functionrenderError(error){
924+
if(!(errorinstanceofError)){
925+
return`${JSON.stringify(error)}`;
926+
}
927+
letresult=`
928+
is error: ${errorinstanceofAggregateError ? 'AggregateError' : 'Error'}
929+
name: ${error.name}
930+
message: ${error.message}
931+
stack: ${normalizeCodeLocInfo(error.stack).split('\n').slice(0,2).join('\n')}
932+
environmentName: ${error.environmentName}
933+
cause: ${'cause'inerror ? renderError(error.cause) : 'no cause'}`;
934+
if('errors'inerror){
935+
result+=`
936+
errors: [${error.errors.map(e=>renderError(e)).join(',\n')}]`;
937+
}
938+
returnresult;
939+
}
940+
941+
functionServerComponent(){
942+
consterror1=newTypeError('first error');
943+
consterror2=newRangeError('second error');
944+
consterror3=newError('third error');
945+
consterror4=newError('fourth error');
946+
consterror5=newError('fifth error');
947+
consterror6=newError('sixth error');
948+
consterror=newAggregateError(
949+
[error1,error2,error3,error4,error5,error6],
950+
'aggregate',
951+
);
952+
throwerror;
953+
}
954+
955+
consttransport=ReactNoopFlightServer.render(<ServerComponent/>,{
956+
onError(x){
957+
if(__DEV__){
958+
return'a dev digest';
959+
}
960+
return`digest("${x.message}")`;
961+
},
962+
});
963+
964+
leterror;
965+
try{
966+
awaitact(()=>{
967+
ReactNoop.render(ReactNoopFlightClient.read(transport));
968+
});
969+
}catch(x){
970+
error=x;
971+
}
972+
973+
if(__DEV__){
974+
expect(renderError(error)).toEqual(`
975+
is error: AggregateError
976+
name: AggregateError
977+
message: aggregate
978+
stack: AggregateError: aggregate
979+
in ServerComponent (at **)
980+
environmentName: Server
981+
cause: no cause
982+
errors: [
983+
is error: Error
984+
name: TypeError
985+
message: first error
986+
stack: TypeError: first error
987+
in ServerComponent (at **)
988+
environmentName: Server
989+
cause: no cause,
990+
991+
is error: Error
992+
name: RangeError
993+
message: second error
994+
stack: RangeError: second error
995+
in ServerComponent (at **)
996+
environmentName: Server
997+
cause: no cause,
998+
999+
is error: Error
1000+
name: Error
1001+
message: third error
1002+
stack: Error: third error
1003+
in ServerComponent (at **)
1004+
environmentName: Server
1005+
cause: no cause,
1006+
1007+
is error: Error
1008+
name: Error
1009+
message: fourth error
1010+
stack: Error: fourth error
1011+
in ServerComponent (at **)
1012+
environmentName: Server
1013+
cause: no cause,
1014+
1015+
is error: Error
1016+
name: Error
1017+
message: fifth error
1018+
stack: Error: fifth error
1019+
in ServerComponent (at **)
1020+
environmentName: Server
1021+
cause: no cause,
1022+
1023+
is error: Error
1024+
name: Error
1025+
message: sixth error
1026+
stack: Error: sixth error
1027+
in ServerComponent (at **)
1028+
environmentName: Server
1029+
cause: no cause]`);
1030+
}else{
1031+
expect(renderError(error)).toEqual(`
1032+
is error: Error
1033+
name: Error
1034+
message: 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.
1035+
stack: Error: 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.
1036+
environmentName: undefined
1037+
cause: no cause`);
1038+
}
1039+
});
1040+
8431041
it('can transport cyclic objects',async()=>{
8441042
functionComponentClient({prop}){
8451043
expect(prop.obj.obj.obj).toBe(prop.obj.obj);

‎packages/react-dom/src/__tests__/ReactDOMSelect-test.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1485,7 +1485,6 @@ describe('ReactDOMSelect', () => {
14851485
);
14861486
}),
14871487
).rejects.toThrowError(
1488-
// eslint-disable-next-line no-undef
14891488
newAggregateError([
14901489
newTypeError('prod message'),
14911490
newTypeError('prod message'),

‎packages/react-reconciler/src/__tests__/ReactFlushSync-test.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,6 @@ describe('ReactFlushSync', () => {
337337
expect(getVisibleChildren(container3)).toEqual('aww');
338338

339339
// Because there were multiple errors, React threw an AggregateError.
340-
// eslint-disable-next-line no-undef
341340
expect(error).toBeInstanceOf(AggregateError);
342341
expect(error.errors.length).toBe(2);
343342
expect(error.errors[0]).toBe(aahh);

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4169,6 +4169,14 @@ function serializeErrorValue(request: Request, error: Error): string {
41694169
constcauseId=outlineModel(request,cause);
41704170
errorInfo.cause=serializeByValueID(causeId);
41714171
}
4172+
if(
4173+
typeofAggregateError!=='undefined'&&
4174+
errorinstanceofAggregateError
4175+
){
4176+
consterrors: ReactClientValue=(error.errors: any);
4177+
consterrorsId=outlineModel(request,errors);
4178+
errorInfo.errors=serializeByValueID(errorsId);
4179+
}
41724180
constid=outlineModel(request,errorInfo);
41734181
return'$Z'+id.toString(16);
41744182
}else{
@@ -4211,6 +4219,15 @@ function serializeDebugErrorValue(
42114219
constcauseId=outlineDebugModel(request,counter,cause);
42124220
errorInfo.cause=serializeByValueID(causeId);
42134221
}
4222+
if(
4223+
typeofAggregateError!=='undefined'&&
4224+
errorinstanceofAggregateError
4225+
){
4226+
counter.objectLimit--;
4227+
consterrors: ReactClientValue=(error.errors: any);
4228+
consterrorsId=outlineDebugModel(request,counter,errors);
4229+
errorInfo.errors=serializeByValueID(errorsId);
4230+
}
42144231
constid=outlineDebugModel(
42154232
request,
42164233
{objectLimit: stack.length*2+1},
@@ -4240,6 +4257,7 @@ function emitErrorChunk(
42404257
letstack: ReactStackTrace;
42414258
letenv=(0,request.environmentName)();
42424259
letcauseReference: null|string=null;
4260+
leterrorsReference: null|string=null;
42434261
try{
42444262
if(errorinstanceofError){
42454263
name=error.name;
@@ -4259,6 +4277,16 @@ function emitErrorChunk(
42594277
: outlineModel(request,cause);
42604278
causeReference=serializeByValueID(causeId);
42614279
}
4280+
if(
4281+
typeofAggregateError!=='undefined'&&
4282+
errorinstanceofAggregateError
4283+
){
4284+
consterrors: ReactClientValue=(error.errors: any);
4285+
consterrorsId=debug
4286+
? outlineDebugModel(request,{objectLimit: 5},errors)
4287+
: outlineModel(request,errors);
4288+
errorsReference=serializeByValueID(errorsId);
4289+
}
42624290
}elseif(typeoferror==='object'&&error!==null){
42634291
message=describeObjectForErrorMessage(error);
42644292
stack=[];
@@ -4277,6 +4305,9 @@ function emitErrorChunk(
42774305
if(causeReference!==null){
42784306
(errorInfo: ReactErrorInfoDev).cause=causeReference;
42794307
}
4308+
if(errorsReference!==null){
4309+
(errorInfo: ReactErrorInfoDev).errors=errorsReference;
4310+
}
42804311
}else{
42814312
errorInfo ={digest};
42824313
}

‎packages/react/src/ReactAct.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ let didWarnNoAwaitAct = false;
2323

2424
functionaggregateErrors(errors: Array<mixed>): mixed{
2525
if(errors.length>1&&typeofAggregateError==='function'){
26-
// eslint-disable-next-line no-undef
2726
returnnewAggregateError(errors);
2827
}
2928
returnerrors[0];

‎packages/shared/ReactTypes.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ export type ReactErrorInfoDev = {
244244
+env: string,
245245
+owner?: null|string,
246246
cause?: JSONValue,
247+
errors?: JSONValue,
247248
};
248249

249250
export type ReactErrorInfo = ReactErrorInfoProd | ReactErrorInfoDev;

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 74568e8

Browse files
authored
[Flight] Transport AggregateErrors.errors (#36156)
1 parent 9627b5a commit 74568e8

10 files changed

Lines changed: 262 additions & 13 deletions

File tree

‎.eslintrc.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,7 @@ module.exports = {
566566
CallSite: 'readonly',
567567
ConsoleTask: 'readonly',// TOOD: Figure out what the official name of this will be.
568568
ReturnType: 'readonly',
569+
AggregateError: 'readonly',
569570
AnimationFrameID: 'readonly',
570571
WeakRef: 'readonly',
571572
// For Flow type annotation. Only `BigInt` is valid at runtime.

‎packages/internal-test-utils/ReactInternalTestUtils.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ ${diff(expectedLog, actualLog)}
122122

123123
functionaggregateErrors(errors: Array<mixed>): mixed{
124124
if(errors.length>1&&typeofAggregateError==='function'){
125-
// eslint-disable-next-line no-undef
126125
returnnewAggregateError(errors);
127126
}
128127
returnerrors[0];

‎packages/internal-test-utils/internalAct.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ async function waitForMicrotasks() {
3434

3535
functionaggregateErrors(errors: Array<mixed>): mixed{
3636
if(errors.length>1&&typeofAggregateError==='function'){
37-
// eslint-disable-next-line no-undef
3837
returnnewAggregateError(errors);
3938
}
4039
returnerrors[0];

‎packages/react-client/src/ReactFlightClient.js‎

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3525,7 +3525,8 @@ function resolveErrorDev(
35253525

35263526
leterror;
35273527
consterrorOptions=
3528-
'cause'inerrorInfo
3528+
// We don't serialize Error.cause in prod so we never need to deserialize
3529+
__DEV__&&'cause'inerrorInfo
35293530
? {
35303531
cause: reviveModel(
35313532
response,
@@ -3536,18 +3537,40 @@ function resolveErrorDev(
35363537
),
35373538
}
35383539
: undefined;
3540+
constisAggregateError=
3541+
typeofAggregateError!=='undefined'&&'errors'inerrorInfo;
3542+
constrevivedErrors=
3543+
// We don't serialize AggregateError.errors in prod so we never need to deserialize
3544+
__DEV__&&isAggregateError
3545+
? reviveModel(
3546+
response,
3547+
// $FlowFixMe[incompatible-cast]
3548+
(errorInfo.errors: JSONValue),
3549+
errorInfo,
3550+
'errors',
3551+
)
3552+
: null;
35393553
constcallStack=buildFakeCallStack(
35403554
response,
35413555
stack,
35423556
env,
35433557
false,
3544-
// $FlowFixMe[incompatible-use]
3545-
Error.bind(
3546-
null,
3547-
message||
3548-
'An error occurred in the Server Components render but no message was provided',
3549-
errorOptions,
3550-
),
3558+
isAggregateError
3559+
? // $FlowFixMe[incompatible-use]
3560+
AggregateError.bind(
3561+
null,
3562+
revivedErrors,
3563+
message||
3564+
'An error occurred in the Server Components render but no message was provided',
3565+
errorOptions,
3566+
)
3567+
: // $FlowFixMe[incompatible-use]
3568+
Error.bind(
3569+
null,
3570+
message||
3571+
'An error occurred in the Server Components render but no message was provided',
3572+
errorOptions,
3573+
),
35513574
);
35523575

35533576
letownerTask: null|ConsoleTask=null;

‎packages/react-client/src/__tests__/ReactFlight-test.js‎

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,204 @@ describe('ReactFlight', () => {
840840
}
841841
});
842842

843+
it('can transport AggregateError',async()=>{
844+
functionrenderError(error){
845+
if(!(errorinstanceofError)){
846+
return`${JSON.stringify(error)}`;
847+
}
848+
letresult=`
849+
is error: ${errorinstanceofAggregateError ? 'AggregateError' : 'Error'}
850+
name: ${error.name}
851+
message: ${error.message}
852+
stack: ${normalizeCodeLocInfo(error.stack).split('\n').slice(0,2).join('\n')}
853+
environmentName: ${error.environmentName}
854+
cause: ${'cause'inerror ? renderError(error.cause) : 'no cause'}`;
855+
if('errors'inerror){
856+
result+=`
857+
errors: [${error.errors.map(e=>renderError(e)).join(',\n')}]`;
858+
}
859+
returnresult;
860+
}
861+
functionComponentClient({error}){
862+
returnrenderError(error);
863+
}
864+
constComponent=clientReference(ComponentClient);
865+
866+
functionServerComponent(){
867+
consterror1=newTypeError('first error');
868+
consterror2=newRangeError('second error');
869+
consterror=newAggregateError([error1,error2],'aggregate');
870+
return<Componenterror={error}/>;
871+
}
872+
873+
consttransport=ReactNoopFlightServer.render(<ServerComponent/>,{
874+
onError(x){
875+
if(__DEV__){
876+
return'a dev digest';
877+
}
878+
return`digest("${x.message}")`;
879+
},
880+
});
881+
882+
awaitact(()=>{
883+
ReactNoop.render(ReactNoopFlightClient.read(transport));
884+
});
885+
886+
if(__DEV__){
887+
expect(ReactNoop).toMatchRenderedOutput(`
888+
is error: AggregateError
889+
name: AggregateError
890+
message: aggregate
891+
stack: AggregateError: aggregate
892+
in ServerComponent (at **)
893+
environmentName: Server
894+
cause: no cause
895+
errors: [
896+
is error: Error
897+
name: TypeError
898+
message: first error
899+
stack: TypeError: first error
900+
in ServerComponent (at **)
901+
environmentName: Server
902+
cause: no cause,
903+
904+
is error: Error
905+
name: RangeError
906+
message: second error
907+
stack: RangeError: second error
908+
in ServerComponent (at **)
909+
environmentName: Server
910+
cause: no cause]`);
911+
}else{
912+
expect(ReactNoop).toMatchRenderedOutput(`
913+
is error: Error
914+
name: Error
915+
message: 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.
916+
stack: Error: 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.
917+
environmentName: undefined
918+
cause: no cause`);
919+
}
920+
});
921+
922+
it('includes AggregateError.errors in thrown errors',async()=>{
923+
functionrenderError(error){
924+
if(!(errorinstanceofError)){
925+
return`${JSON.stringify(error)}`;
926+
}
927+
letresult=`
928+
is error: ${errorinstanceofAggregateError ? 'AggregateError' : 'Error'}
929+
name: ${error.name}
930+
message: ${error.message}
931+
stack: ${normalizeCodeLocInfo(error.stack).split('\n').slice(0,2).join('\n')}
932+
environmentName: ${error.environmentName}
933+
cause: ${'cause'inerror ? renderError(error.cause) : 'no cause'}`;
934+
if('errors'inerror){
935+
result+=`
936+
errors: [${error.errors.map(e=>renderError(e)).join(',\n')}]`;
937+
}
938+
returnresult;
939+
}
940+
941+
functionServerComponent(){
942+
consterror1=newTypeError('first error');
943+
consterror2=newRangeError('second error');
944+
consterror3=newError('third error');
945+
consterror4=newError('fourth error');
946+
consterror5=newError('fifth error');
947+
consterror6=newError('sixth error');
948+
consterror=newAggregateError(
949+
[error1,error2,error3,error4,error5,error6],
950+
'aggregate',
951+
);
952+
throwerror;
953+
}
954+
955+
consttransport=ReactNoopFlightServer.render(<ServerComponent/>,{
956+
onError(x){
957+
if(__DEV__){
958+
return'a dev digest';
959+
}
960+
return`digest("${x.message}")`;
961+
},
962+
});
963+
964+
leterror;
965+
try{
966+
awaitact(()=>{
967+
ReactNoop.render(ReactNoopFlightClient.read(transport));
968+
});
969+
}catch(x){
970+
error=x;
971+
}
972+
973+
if(__DEV__){
974+
expect(renderError(error)).toEqual(`
975+
is error: AggregateError
976+
name: AggregateError
977+
message: aggregate
978+
stack: AggregateError: aggregate
979+
in ServerComponent (at **)
980+
environmentName: Server
981+
cause: no cause
982+
errors: [
983+
is error: Error
984+
name: TypeError
985+
message: first error
986+
stack: TypeError: first error
987+
in ServerComponent (at **)
988+
environmentName: Server
989+
cause: no cause,
990+
991+
is error: Error
992+
name: RangeError
993+
message: second error
994+
stack: RangeError: second error
995+
in ServerComponent (at **)
996+
environmentName: Server
997+
cause: no cause,
998+
999+
is error: Error
1000+
name: Error
1001+
message: third error
1002+
stack: Error: third error
1003+
in ServerComponent (at **)
1004+
environmentName: Server
1005+
cause: no cause,
1006+
1007+
is error: Error
1008+
name: Error
1009+
message: fourth error
1010+
stack: Error: fourth error
1011+
in ServerComponent (at **)
1012+
environmentName: Server
1013+
cause: no cause,
1014+
1015+
is error: Error
1016+
name: Error
1017+
message: fifth error
1018+
stack: Error: fifth error
1019+
in ServerComponent (at **)
1020+
environmentName: Server
1021+
cause: no cause,
1022+
1023+
is error: Error
1024+
name: Error
1025+
message: sixth error
1026+
stack: Error: sixth error
1027+
in ServerComponent (at **)
1028+
environmentName: Server
1029+
cause: no cause]`);
1030+
}else{
1031+
expect(renderError(error)).toEqual(`
1032+
is error: Error
1033+
name: Error
1034+
message: 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.
1035+
stack: Error: 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.
1036+
environmentName: undefined
1037+
cause: no cause`);
1038+
}
1039+
});
1040+
8431041
it('can transport cyclic objects',async()=>{
8441042
functionComponentClient({prop}){
8451043
expect(prop.obj.obj.obj).toBe(prop.obj.obj);

‎packages/react-dom/src/__tests__/ReactDOMSelect-test.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1485,7 +1485,6 @@ describe('ReactDOMSelect', () => {
14851485
);
14861486
}),
14871487
).rejects.toThrowError(
1488-
// eslint-disable-next-line no-undef
14891488
newAggregateError([
14901489
newTypeError('prod message'),
14911490
newTypeError('prod message'),

‎packages/react-reconciler/src/__tests__/ReactFlushSync-test.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,6 @@ describe('ReactFlushSync', () => {
337337
expect(getVisibleChildren(container3)).toEqual('aww');
338338

339339
// Because there were multiple errors, React threw an AggregateError.
340-
// eslint-disable-next-line no-undef
341340
expect(error).toBeInstanceOf(AggregateError);
342341
expect(error.errors.length).toBe(2);
343342
expect(error.errors[0]).toBe(aahh);

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4169,6 +4169,14 @@ function serializeErrorValue(request: Request, error: Error): string {
41694169
constcauseId=outlineModel(request,cause);
41704170
errorInfo.cause=serializeByValueID(causeId);
41714171
}
4172+
if(
4173+
typeofAggregateError!=='undefined'&&
4174+
errorinstanceofAggregateError
4175+
){
4176+
consterrors: ReactClientValue=(error.errors: any);
4177+
consterrorsId=outlineModel(request,errors);
4178+
errorInfo.errors=serializeByValueID(errorsId);
4179+
}
41724180
constid=outlineModel(request,errorInfo);
41734181
return'$Z'+id.toString(16);
41744182
}else{
@@ -4211,6 +4219,15 @@ function serializeDebugErrorValue(
42114219
constcauseId=outlineDebugModel(request,counter,cause);
42124220
errorInfo.cause=serializeByValueID(causeId);
42134221
}
4222+
if(
4223+
typeofAggregateError!=='undefined'&&
4224+
errorinstanceofAggregateError
4225+
){
4226+
counter.objectLimit--;
4227+
consterrors: ReactClientValue=(error.errors: any);
4228+
consterrorsId=outlineDebugModel(request,counter,errors);
4229+
errorInfo.errors=serializeByValueID(errorsId);
4230+
}
42144231
constid=outlineDebugModel(
42154232
request,
42164233
{objectLimit: stack.length*2+1},
@@ -4240,6 +4257,7 @@ function emitErrorChunk(
42404257
letstack: ReactStackTrace;
42414258
letenv=(0,request.environmentName)();
42424259
letcauseReference: null|string=null;
4260+
leterrorsReference: null|string=null;
42434261
try{
42444262
if(errorinstanceofError){
42454263
name=error.name;
@@ -4259,6 +4277,16 @@ function emitErrorChunk(
42594277
: outlineModel(request,cause);
42604278
causeReference=serializeByValueID(causeId);
42614279
}
4280+
if(
4281+
typeofAggregateError!=='undefined'&&
4282+
errorinstanceofAggregateError
4283+
){
4284+
consterrors: ReactClientValue=(error.errors: any);
4285+
consterrorsId=debug
4286+
? outlineDebugModel(request,{objectLimit: 5},errors)
4287+
: outlineModel(request,errors);
4288+
errorsReference=serializeByValueID(errorsId);
4289+
}
42624290
}elseif(typeoferror==='object'&&error!==null){
42634291
message=describeObjectForErrorMessage(error);
42644292
stack=[];
@@ -4277,6 +4305,9 @@ function emitErrorChunk(
42774305
if(causeReference!==null){
42784306
(errorInfo: ReactErrorInfoDev).cause=causeReference;
42794307
}
4308+
if(errorsReference!==null){
4309+
(errorInfo: ReactErrorInfoDev).errors=errorsReference;
4310+
}
42804311
}else{
42814312
errorInfo ={digest};
42824313
}

‎packages/react/src/ReactAct.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ let didWarnNoAwaitAct = false;
2323

2424
functionaggregateErrors(errors: Array<mixed>): mixed{
2525
if(errors.length>1&&typeofAggregateError==='function'){
26-
// eslint-disable-next-line no-undef
2726
returnnewAggregateError(errors);
2827
}
2928
returnerrors[0];

‎packages/shared/ReactTypes.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ export type ReactErrorInfoDev = {
244244
+env: string,
245245
+owner?: null|string,
246246
cause?: JSONValue,
247+
errors?: JSONValue,
247248
};
248249

249250
export type ReactErrorInfo = ReactErrorInfoProd | ReactErrorInfoDev;

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 74568e8

Browse files
authored
[Flight] Transport AggregateErrors.errors (#36156)
1 parent 9627b5a commit 74568e8

10 files changed

Lines changed: 262 additions & 13 deletions

File tree

‎.eslintrc.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,7 @@ module.exports = {
566566
CallSite: 'readonly',
567567
ConsoleTask: 'readonly',// TOOD: Figure out what the official name of this will be.
568568
ReturnType: 'readonly',
569+
AggregateError: 'readonly',
569570
AnimationFrameID: 'readonly',
570571
WeakRef: 'readonly',
571572
// For Flow type annotation. Only `BigInt` is valid at runtime.

‎packages/internal-test-utils/ReactInternalTestUtils.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ ${diff(expectedLog, actualLog)}
122122

123123
functionaggregateErrors(errors: Array<mixed>): mixed{
124124
if(errors.length>1&&typeofAggregateError==='function'){
125-
// eslint-disable-next-line no-undef
126125
returnnewAggregateError(errors);
127126
}
128127
returnerrors[0];

‎packages/internal-test-utils/internalAct.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ async function waitForMicrotasks() {
3434

3535
functionaggregateErrors(errors: Array<mixed>): mixed{
3636
if(errors.length>1&&typeofAggregateError==='function'){
37-
// eslint-disable-next-line no-undef
3837
returnnewAggregateError(errors);
3938
}
4039
returnerrors[0];

‎packages/react-client/src/ReactFlightClient.js‎

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3525,7 +3525,8 @@ function resolveErrorDev(
35253525

35263526
leterror;
35273527
consterrorOptions=
3528-
'cause'inerrorInfo
3528+
// We don't serialize Error.cause in prod so we never need to deserialize
3529+
__DEV__&&'cause'inerrorInfo
35293530
? {
35303531
cause: reviveModel(
35313532
response,
@@ -3536,18 +3537,40 @@ function resolveErrorDev(
35363537
),
35373538
}
35383539
: undefined;
3540+
constisAggregateError=
3541+
typeofAggregateError!=='undefined'&&'errors'inerrorInfo;
3542+
constrevivedErrors=
3543+
// We don't serialize AggregateError.errors in prod so we never need to deserialize
3544+
__DEV__&&isAggregateError
3545+
? reviveModel(
3546+
response,
3547+
// $FlowFixMe[incompatible-cast]
3548+
(errorInfo.errors: JSONValue),
3549+
errorInfo,
3550+
'errors',
3551+
)
3552+
: null;
35393553
constcallStack=buildFakeCallStack(
35403554
response,
35413555
stack,
35423556
env,
35433557
false,
3544-
// $FlowFixMe[incompatible-use]
3545-
Error.bind(
3546-
null,
3547-
message||
3548-
'An error occurred in the Server Components render but no message was provided',
3549-
errorOptions,
3550-
),
3558+
isAggregateError
3559+
? // $FlowFixMe[incompatible-use]
3560+
AggregateError.bind(
3561+
null,
3562+
revivedErrors,
3563+
message||
3564+
'An error occurred in the Server Components render but no message was provided',
3565+
errorOptions,
3566+
)
3567+
: // $FlowFixMe[incompatible-use]
3568+
Error.bind(
3569+
null,
3570+
message||
3571+
'An error occurred in the Server Components render but no message was provided',
3572+
errorOptions,
3573+
),
35513574
);
35523575

35533576
letownerTask: null|ConsoleTask=null;

‎packages/react-client/src/__tests__/ReactFlight-test.js‎

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,204 @@ describe('ReactFlight', () => {
840840
}
841841
});
842842

843+
it('can transport AggregateError',async()=>{
844+
functionrenderError(error){
845+
if(!(errorinstanceofError)){
846+
return`${JSON.stringify(error)}`;
847+
}
848+
letresult=`
849+
is error: ${errorinstanceofAggregateError ? 'AggregateError' : 'Error'}
850+
name: ${error.name}
851+
message: ${error.message}
852+
stack: ${normalizeCodeLocInfo(error.stack).split('\n').slice(0,2).join('\n')}
853+
environmentName: ${error.environmentName}
854+
cause: ${'cause'inerror ? renderError(error.cause) : 'no cause'}`;
855+
if('errors'inerror){
856+
result+=`
857+
errors: [${error.errors.map(e=>renderError(e)).join(',\n')}]`;
858+
}
859+
returnresult;
860+
}
861+
functionComponentClient({error}){
862+
returnrenderError(error);
863+
}
864+
constComponent=clientReference(ComponentClient);
865+
866+
functionServerComponent(){
867+
consterror1=newTypeError('first error');
868+
consterror2=newRangeError('second error');
869+
consterror=newAggregateError([error1,error2],'aggregate');
870+
return<Componenterror={error}/>;
871+
}
872+
873+
consttransport=ReactNoopFlightServer.render(<ServerComponent/>,{
874+
onError(x){
875+
if(__DEV__){
876+
return'a dev digest';
877+
}
878+
return`digest("${x.message}")`;
879+
},
880+
});
881+
882+
awaitact(()=>{
883+
ReactNoop.render(ReactNoopFlightClient.read(transport));
884+
});
885+
886+
if(__DEV__){
887+
expect(ReactNoop).toMatchRenderedOutput(`
888+
is error: AggregateError
889+
name: AggregateError
890+
message: aggregate
891+
stack: AggregateError: aggregate
892+
in ServerComponent (at **)
893+
environmentName: Server
894+
cause: no cause
895+
errors: [
896+
is error: Error
897+
name: TypeError
898+
message: first error
899+
stack: TypeError: first error
900+
in ServerComponent (at **)
901+
environmentName: Server
902+
cause: no cause,
903+
904+
is error: Error
905+
name: RangeError
906+
message: second error
907+
stack: RangeError: second error
908+
in ServerComponent (at **)
909+
environmentName: Server
910+
cause: no cause]`);
911+
}else{
912+
expect(ReactNoop).toMatchRenderedOutput(`
913+
is error: Error
914+
name: Error
915+
message: 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.
916+
stack: Error: 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.
917+
environmentName: undefined
918+
cause: no cause`);
919+
}
920+
});
921+
922+
it('includes AggregateError.errors in thrown errors',async()=>{
923+
functionrenderError(error){
924+
if(!(errorinstanceofError)){
925+
return`${JSON.stringify(error)}`;
926+
}
927+
letresult=`
928+
is error: ${errorinstanceofAggregateError ? 'AggregateError' : 'Error'}
929+
name: ${error.name}
930+
message: ${error.message}
931+
stack: ${normalizeCodeLocInfo(error.stack).split('\n').slice(0,2).join('\n')}
932+
environmentName: ${error.environmentName}
933+
cause: ${'cause'inerror ? renderError(error.cause) : 'no cause'}`;
934+
if('errors'inerror){
935+
result+=`
936+
errors: [${error.errors.map(e=>renderError(e)).join(',\n')}]`;
937+
}
938+
returnresult;
939+
}
940+
941+
functionServerComponent(){
942+
consterror1=newTypeError('first error');
943+
consterror2=newRangeError('second error');
944+
consterror3=newError('third error');
945+
consterror4=newError('fourth error');
946+
consterror5=newError('fifth error');
947+
consterror6=newError('sixth error');
948+
consterror=newAggregateError(
949+
[error1,error2,error3,error4,error5,error6],
950+
'aggregate',
951+
);
952+
throwerror;
953+
}
954+
955+
consttransport=ReactNoopFlightServer.render(<ServerComponent/>,{
956+
onError(x){
957+
if(__DEV__){
958+
return'a dev digest';
959+
}
960+
return`digest("${x.message}")`;
961+
},
962+
});
963+
964+
leterror;
965+
try{
966+
awaitact(()=>{
967+
ReactNoop.render(ReactNoopFlightClient.read(transport));
968+
});
969+
}catch(x){
970+
error=x;
971+
}
972+
973+
if(__DEV__){
974+
expect(renderError(error)).toEqual(`
975+
is error: AggregateError
976+
name: AggregateError
977+
message: aggregate
978+
stack: AggregateError: aggregate
979+
in ServerComponent (at **)
980+
environmentName: Server
981+
cause: no cause
982+
errors: [
983+
is error: Error
984+
name: TypeError
985+
message: first error
986+
stack: TypeError: first error
987+
in ServerComponent (at **)
988+
environmentName: Server
989+
cause: no cause,
990+
991+
is error: Error
992+
name: RangeError
993+
message: second error
994+
stack: RangeError: second error
995+
in ServerComponent (at **)
996+
environmentName: Server
997+
cause: no cause,
998+
999+
is error: Error
1000+
name: Error
1001+
message: third error
1002+
stack: Error: third error
1003+
in ServerComponent (at **)
1004+
environmentName: Server
1005+
cause: no cause,
1006+
1007+
is error: Error
1008+
name: Error
1009+
message: fourth error
1010+
stack: Error: fourth error
1011+
in ServerComponent (at **)
1012+
environmentName: Server
1013+
cause: no cause,
1014+
1015+
is error: Error
1016+
name: Error
1017+
message: fifth error
1018+
stack: Error: fifth error
1019+
in ServerComponent (at **)
1020+
environmentName: Server
1021+
cause: no cause,
1022+
1023+
is error: Error
1024+
name: Error
1025+
message: sixth error
1026+
stack: Error: sixth error
1027+
in ServerComponent (at **)
1028+
environmentName: Server
1029+
cause: no cause]`);
1030+
}else{
1031+
expect(renderError(error)).toEqual(`
1032+
is error: Error
1033+
name: Error
1034+
message: 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.
1035+
stack: Error: 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.
1036+
environmentName: undefined
1037+
cause: no cause`);
1038+
}
1039+
});
1040+
8431041
it('can transport cyclic objects',async()=>{
8441042
functionComponentClient({prop}){
8451043
expect(prop.obj.obj.obj).toBe(prop.obj.obj);

‎packages/react-dom/src/__tests__/ReactDOMSelect-test.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1485,7 +1485,6 @@ describe('ReactDOMSelect', () => {
14851485
);
14861486
}),
14871487
).rejects.toThrowError(
1488-
// eslint-disable-next-line no-undef
14891488
newAggregateError([
14901489
newTypeError('prod message'),
14911490
newTypeError('prod message'),

‎packages/react-reconciler/src/__tests__/ReactFlushSync-test.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,6 @@ describe('ReactFlushSync', () => {
337337
expect(getVisibleChildren(container3)).toEqual('aww');
338338

339339
// Because there were multiple errors, React threw an AggregateError.
340-
// eslint-disable-next-line no-undef
341340
expect(error).toBeInstanceOf(AggregateError);
342341
expect(error.errors.length).toBe(2);
343342
expect(error.errors[0]).toBe(aahh);

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4169,6 +4169,14 @@ function serializeErrorValue(request: Request, error: Error): string {
41694169
constcauseId=outlineModel(request,cause);
41704170
errorInfo.cause=serializeByValueID(causeId);
41714171
}
4172+
if(
4173+
typeofAggregateError!=='undefined'&&
4174+
errorinstanceofAggregateError
4175+
){
4176+
consterrors: ReactClientValue=(error.errors: any);
4177+
consterrorsId=outlineModel(request,errors);
4178+
errorInfo.errors=serializeByValueID(errorsId);
4179+
}
41724180
constid=outlineModel(request,errorInfo);
41734181
return'$Z'+id.toString(16);
41744182
}else{
@@ -4211,6 +4219,15 @@ function serializeDebugErrorValue(
42114219
constcauseId=outlineDebugModel(request,counter,cause);
42124220
errorInfo.cause=serializeByValueID(causeId);
42134221
}
4222+
if(
4223+
typeofAggregateError!=='undefined'&&
4224+
errorinstanceofAggregateError
4225+
){
4226+
counter.objectLimit--;
4227+
consterrors: ReactClientValue=(error.errors: any);
4228+
consterrorsId=outlineDebugModel(request,counter,errors);
4229+
errorInfo.errors=serializeByValueID(errorsId);
4230+
}
42144231
constid=outlineDebugModel(
42154232
request,
42164233
{objectLimit: stack.length*2+1},
@@ -4240,6 +4257,7 @@ function emitErrorChunk(
42404257
letstack: ReactStackTrace;
42414258
letenv=(0,request.environmentName)();
42424259
letcauseReference: null|string=null;
4260+
leterrorsReference: null|string=null;
42434261
try{
42444262
if(errorinstanceofError){
42454263
name=error.name;
@@ -4259,6 +4277,16 @@ function emitErrorChunk(
42594277
: outlineModel(request,cause);
42604278
causeReference=serializeByValueID(causeId);
42614279
}
4280+
if(
4281+
typeofAggregateError!=='undefined'&&
4282+
errorinstanceofAggregateError
4283+
){
4284+
consterrors: ReactClientValue=(error.errors: any);
4285+
consterrorsId=debug
4286+
? outlineDebugModel(request,{objectLimit: 5},errors)
4287+
: outlineModel(request,errors);
4288+
errorsReference=serializeByValueID(errorsId);
4289+
}
42624290
}elseif(typeoferror==='object'&&error!==null){
42634291
message=describeObjectForErrorMessage(error);
42644292
stack=[];
@@ -4277,6 +4305,9 @@ function emitErrorChunk(
42774305
if(causeReference!==null){
42784306
(errorInfo: ReactErrorInfoDev).cause=causeReference;
42794307
}
4308+
if(errorsReference!==null){
4309+
(errorInfo: ReactErrorInfoDev).errors=errorsReference;
4310+
}
42804311
}else{
42814312
errorInfo ={digest};
42824313
}

‎packages/react/src/ReactAct.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ let didWarnNoAwaitAct = false;
2323

2424
functionaggregateErrors(errors: Array<mixed>): mixed{
2525
if(errors.length>1&&typeofAggregateError==='function'){
26-
// eslint-disable-next-line no-undef
2726
returnnewAggregateError(errors);
2827
}
2928
returnerrors[0];

‎packages/shared/ReactTypes.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ export type ReactErrorInfoDev = {
244244
+env: string,
245245
+owner?: null|string,
246246
cause?: JSONValue,
247+
errors?: JSONValue,
247248
};
248249

249250
export type ReactErrorInfo = ReactErrorInfoProd | ReactErrorInfoDev;

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Commit 74568e8

Browse files
authored
[Flight] Transport AggregateErrors.errors (#36156)
1 parent 9627b5a commit 74568e8

10 files changed

Lines changed: 262 additions & 13 deletions

File tree

‎.eslintrc.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,7 @@ module.exports = {
566566
CallSite: 'readonly',
567567
ConsoleTask: 'readonly',// TOOD: Figure out what the official name of this will be.
568568
ReturnType: 'readonly',
569+
AggregateError: 'readonly',
569570
AnimationFrameID: 'readonly',
570571
WeakRef: 'readonly',
571572
// For Flow type annotation. Only `BigInt` is valid at runtime.

‎packages/internal-test-utils/ReactInternalTestUtils.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ ${diff(expectedLog, actualLog)}
122122

123123
functionaggregateErrors(errors: Array<mixed>): mixed{
124124
if(errors.length>1&&typeofAggregateError==='function'){
125-
// eslint-disable-next-line no-undef
126125
returnnewAggregateError(errors);
127126
}
128127
returnerrors[0];

‎packages/internal-test-utils/internalAct.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ async function waitForMicrotasks() {
3434

3535
functionaggregateErrors(errors: Array<mixed>): mixed{
3636
if(errors.length>1&&typeofAggregateError==='function'){
37-
// eslint-disable-next-line no-undef
3837
returnnewAggregateError(errors);
3938
}
4039
returnerrors[0];

‎packages/react-client/src/ReactFlightClient.js‎

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3525,7 +3525,8 @@ function resolveErrorDev(
35253525

35263526
leterror;
35273527
consterrorOptions=
3528-
'cause'inerrorInfo
3528+
// We don't serialize Error.cause in prod so we never need to deserialize
3529+
__DEV__&&'cause'inerrorInfo
35293530
? {
35303531
cause: reviveModel(
35313532
response,
@@ -3536,18 +3537,40 @@ function resolveErrorDev(
35363537
),
35373538
}
35383539
: undefined;
3540+
constisAggregateError=
3541+
typeofAggregateError!=='undefined'&&'errors'inerrorInfo;
3542+
constrevivedErrors=
3543+
// We don't serialize AggregateError.errors in prod so we never need to deserialize
3544+
__DEV__&&isAggregateError
3545+
? reviveModel(
3546+
response,
3547+
// $FlowFixMe[incompatible-cast]
3548+
(errorInfo.errors: JSONValue),
3549+
errorInfo,
3550+
'errors',
3551+
)
3552+
: null;
35393553
constcallStack=buildFakeCallStack(
35403554
response,
35413555
stack,
35423556
env,
35433557
false,
3544-
// $FlowFixMe[incompatible-use]
3545-
Error.bind(
3546-
null,
3547-
message||
3548-
'An error occurred in the Server Components render but no message was provided',
3549-
errorOptions,
3550-
),
3558+
isAggregateError
3559+
? // $FlowFixMe[incompatible-use]
3560+
AggregateError.bind(
3561+
null,
3562+
revivedErrors,
3563+
message||
3564+
'An error occurred in the Server Components render but no message was provided',
3565+
errorOptions,
3566+
)
3567+
: // $FlowFixMe[incompatible-use]
3568+
Error.bind(
3569+
null,
3570+
message||
3571+
'An error occurred in the Server Components render but no message was provided',
3572+
errorOptions,
3573+
),
35513574
);
35523575

35533576
letownerTask: null|ConsoleTask=null;

‎packages/react-client/src/__tests__/ReactFlight-test.js‎

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,204 @@ describe('ReactFlight', () => {
840840
}
841841
});
842842

843+
it('can transport AggregateError',async()=>{
844+
functionrenderError(error){
845+
if(!(errorinstanceofError)){
846+
return`${JSON.stringify(error)}`;
847+
}
848+
letresult=`
849+
is error: ${errorinstanceofAggregateError ? 'AggregateError' : 'Error'}
850+
name: ${error.name}
851+
message: ${error.message}
852+
stack: ${normalizeCodeLocInfo(error.stack).split('\n').slice(0,2).join('\n')}
853+
environmentName: ${error.environmentName}
854+
cause: ${'cause'inerror ? renderError(error.cause) : 'no cause'}`;
855+
if('errors'inerror){
856+
result+=`
857+
errors: [${error.errors.map(e=>renderError(e)).join(',\n')}]`;
858+
}
859+
returnresult;
860+
}
861+
functionComponentClient({error}){
862+
returnrenderError(error);
863+
}
864+
constComponent=clientReference(ComponentClient);
865+
866+
functionServerComponent(){
867+
consterror1=newTypeError('first error');
868+
consterror2=newRangeError('second error');
869+
consterror=newAggregateError([error1,error2],'aggregate');
870+
return<Componenterror={error}/>;
871+
}
872+
873+
consttransport=ReactNoopFlightServer.render(<ServerComponent/>,{
874+
onError(x){
875+
if(__DEV__){
876+
return'a dev digest';
877+
}
878+
return`digest("${x.message}")`;
879+
},
880+
});
881+
882+
awaitact(()=>{
883+
ReactNoop.render(ReactNoopFlightClient.read(transport));
884+
});
885+
886+
if(__DEV__){
887+
expect(ReactNoop).toMatchRenderedOutput(`
888+
is error: AggregateError
889+
name: AggregateError
890+
message: aggregate
891+
stack: AggregateError: aggregate
892+
in ServerComponent (at **)
893+
environmentName: Server
894+
cause: no cause
895+
errors: [
896+
is error: Error
897+
name: TypeError
898+
message: first error
899+
stack: TypeError: first error
900+
in ServerComponent (at **)
901+
environmentName: Server
902+
cause: no cause,
903+
904+
is error: Error
905+
name: RangeError
906+
message: second error
907+
stack: RangeError: second error
908+
in ServerComponent (at **)
909+
environmentName: Server
910+
cause: no cause]`);
911+
}else{
912+
expect(ReactNoop).toMatchRenderedOutput(`
913+
is error: Error
914+
name: Error
915+
message: 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.
916+
stack: Error: 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.
917+
environmentName: undefined
918+
cause: no cause`);
919+
}
920+
});
921+
922+
it('includes AggregateError.errors in thrown errors',async()=>{
923+
functionrenderError(error){
924+
if(!(errorinstanceofError)){
925+
return`${JSON.stringify(error)}`;
926+
}
927+
letresult=`
928+
is error: ${errorinstanceofAggregateError ? 'AggregateError' : 'Error'}
929+
name: ${error.name}
930+
message: ${error.message}
931+
stack: ${normalizeCodeLocInfo(error.stack).split('\n').slice(0,2).join('\n')}
932+
environmentName: ${error.environmentName}
933+
cause: ${'cause'inerror ? renderError(error.cause) : 'no cause'}`;
934+
if('errors'inerror){
935+
result+=`
936+
errors: [${error.errors.map(e=>renderError(e)).join(',\n')}]`;
937+
}
938+
returnresult;
939+
}
940+
941+
functionServerComponent(){
942+
consterror1=newTypeError('first error');
943+
consterror2=newRangeError('second error');
944+
consterror3=newError('third error');
945+
consterror4=newError('fourth error');
946+
consterror5=newError('fifth error');
947+
consterror6=newError('sixth error');
948+
consterror=newAggregateError(
949+
[error1,error2,error3,error4,error5,error6],
950+
'aggregate',
951+
);
952+
throwerror;
953+
}
954+
955+
consttransport=ReactNoopFlightServer.render(<ServerComponent/>,{
956+
onError(x){
957+
if(__DEV__){
958+
return'a dev digest';
959+
}
960+
return`digest("${x.message}")`;
961+
},
962+
});
963+
964+
leterror;
965+
try{
966+
awaitact(()=>{
967+
ReactNoop.render(ReactNoopFlightClient.read(transport));
968+
});
969+
}catch(x){
970+
error=x;
971+
}
972+
973+
if(__DEV__){
974+
expect(renderError(error)).toEqual(`
975+
is error: AggregateError
976+
name: AggregateError
977+
message: aggregate
978+
stack: AggregateError: aggregate
979+
in ServerComponent (at **)
980+
environmentName: Server
981+
cause: no cause
982+
errors: [
983+
is error: Error
984+
name: TypeError
985+
message: first error
986+
stack: TypeError: first error
987+
in ServerComponent (at **)
988+
environmentName: Server
989+
cause: no cause,
990+
991+
is error: Error
992+
name: RangeError
993+
message: second error
994+
stack: RangeError: second error
995+
in ServerComponent (at **)
996+
environmentName: Server
997+
cause: no cause,
998+
999+
is error: Error
1000+
name: Error
1001+
message: third error
1002+
stack: Error: third error
1003+
in ServerComponent (at **)
1004+
environmentName: Server
1005+
cause: no cause,
1006+
1007+
is error: Error
1008+
name: Error
1009+
message: fourth error
1010+
stack: Error: fourth error
1011+
in ServerComponent (at **)
1012+
environmentName: Server
1013+
cause: no cause,
1014+
1015+
is error: Error
1016+
name: Error
1017+
message: fifth error
1018+
stack: Error: fifth error
1019+
in ServerComponent (at **)
1020+
environmentName: Server
1021+
cause: no cause,
1022+
1023+
is error: Error
1024+
name: Error
1025+
message: sixth error
1026+
stack: Error: sixth error
1027+
in ServerComponent (at **)
1028+
environmentName: Server
1029+
cause: no cause]`);
1030+
}else{
1031+
expect(renderError(error)).toEqual(`
1032+
is error: Error
1033+
name: Error
1034+
message: 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.
1035+
stack: Error: 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.
1036+
environmentName: undefined
1037+
cause: no cause`);
1038+
}
1039+
});
1040+
8431041
it('can transport cyclic objects',async()=>{
8441042
functionComponentClient({prop}){
8451043
expect(prop.obj.obj.obj).toBe(prop.obj.obj);

‎packages/react-dom/src/__tests__/ReactDOMSelect-test.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1485,7 +1485,6 @@ describe('ReactDOMSelect', () => {
14851485
);
14861486
}),
14871487
).rejects.toThrowError(
1488-
// eslint-disable-next-line no-undef
14891488
newAggregateError([
14901489
newTypeError('prod message'),
14911490
newTypeError('prod message'),

‎packages/react-reconciler/src/__tests__/ReactFlushSync-test.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,6 @@ describe('ReactFlushSync', () => {
337337
expect(getVisibleChildren(container3)).toEqual('aww');
338338

339339
// Because there were multiple errors, React threw an AggregateError.
340-
// eslint-disable-next-line no-undef
341340
expect(error).toBeInstanceOf(AggregateError);
342341
expect(error.errors.length).toBe(2);
343342
expect(error.errors[0]).toBe(aahh);

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4169,6 +4169,14 @@ function serializeErrorValue(request: Request, error: Error): string {
41694169
constcauseId=outlineModel(request,cause);
41704170
errorInfo.cause=serializeByValueID(causeId);
41714171
}
4172+
if(
4173+
typeofAggregateError!=='undefined'&&
4174+
errorinstanceofAggregateError
4175+
){
4176+
consterrors: ReactClientValue=(error.errors: any);
4177+
consterrorsId=outlineModel(request,errors);
4178+
errorInfo.errors=serializeByValueID(errorsId);
4179+
}
41724180
constid=outlineModel(request,errorInfo);
41734181
return'$Z'+id.toString(16);
41744182
}else{
@@ -4211,6 +4219,15 @@ function serializeDebugErrorValue(
42114219
constcauseId=outlineDebugModel(request,counter,cause);
42124220
errorInfo.cause=serializeByValueID(causeId);
42134221
}
4222+
if(
4223+
typeofAggregateError!=='undefined'&&
4224+
errorinstanceofAggregateError
4225+
){
4226+
counter.objectLimit--;
4227+
consterrors: ReactClientValue=(error.errors: any);
4228+
consterrorsId=outlineDebugModel(request,counter,errors);
4229+
errorInfo.errors=serializeByValueID(errorsId);
4230+
}
42144231
constid=outlineDebugModel(
42154232
request,
42164233
{objectLimit: stack.length*2+1},
@@ -4240,6 +4257,7 @@ function emitErrorChunk(
42404257
letstack: ReactStackTrace;
42414258
letenv=(0,request.environmentName)();
42424259
letcauseReference: null|string=null;
4260+
leterrorsReference: null|string=null;
42434261
try{
42444262
if(errorinstanceofError){
42454263
name=error.name;
@@ -4259,6 +4277,16 @@ function emitErrorChunk(
42594277
: outlineModel(request,cause);
42604278
causeReference=serializeByValueID(causeId);
42614279
}
4280+
if(
4281+
typeofAggregateError!=='undefined'&&
4282+
errorinstanceofAggregateError
4283+
){
4284+
consterrors: ReactClientValue=(error.errors: any);
4285+
consterrorsId=debug
4286+
? outlineDebugModel(request,{objectLimit: 5},errors)
4287+
: outlineModel(request,errors);
4288+
errorsReference=serializeByValueID(errorsId);
4289+
}
42624290
}elseif(typeoferror==='object'&&error!==null){
42634291
message=describeObjectForErrorMessage(error);
42644292
stack=[];
@@ -4277,6 +4305,9 @@ function emitErrorChunk(
42774305
if(causeReference!==null){
42784306
(errorInfo: ReactErrorInfoDev).cause=causeReference;
42794307
}
4308+
if(errorsReference!==null){
4309+
(errorInfo: ReactErrorInfoDev).errors=errorsReference;
4310+
}
42804311
}else{
42814312
errorInfo ={digest};
42824313
}

‎packages/react/src/ReactAct.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ let didWarnNoAwaitAct = false;
2323

2424
functionaggregateErrors(errors: Array<mixed>): mixed{
2525
if(errors.length>1&&typeofAggregateError==='function'){
26-
// eslint-disable-next-line no-undef
2726
returnnewAggregateError(errors);
2827
}
2928
returnerrors[0];

‎packages/shared/ReactTypes.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ export type ReactErrorInfoDev = {
244244
+env: string,
245245
+owner?: null|string,
246246
cause?: JSONValue,
247+
errors?: JSONValue,
247248
};
248249

249250
export type ReactErrorInfo = ReactErrorInfoProd | ReactErrorInfoDev;

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 74568e8

Browse files
authored
[Flight] Transport AggregateErrors.errors (#36156)
1 parent 9627b5a commit 74568e8

10 files changed

Lines changed: 262 additions & 13 deletions

File tree

‎.eslintrc.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,7 @@ module.exports = {
566566
CallSite: 'readonly',
567567
ConsoleTask: 'readonly',// TOOD: Figure out what the official name of this will be.
568568
ReturnType: 'readonly',
569+
AggregateError: 'readonly',
569570
AnimationFrameID: 'readonly',
570571
WeakRef: 'readonly',
571572
// For Flow type annotation. Only `BigInt` is valid at runtime.

‎packages/internal-test-utils/ReactInternalTestUtils.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ ${diff(expectedLog, actualLog)}
122122

123123
functionaggregateErrors(errors: Array<mixed>): mixed{
124124
if(errors.length>1&&typeofAggregateError==='function'){
125-
// eslint-disable-next-line no-undef
126125
returnnewAggregateError(errors);
127126
}
128127
returnerrors[0];

‎packages/internal-test-utils/internalAct.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ async function waitForMicrotasks() {
3434

3535
functionaggregateErrors(errors: Array<mixed>): mixed{
3636
if(errors.length>1&&typeofAggregateError==='function'){
37-
// eslint-disable-next-line no-undef
3837
returnnewAggregateError(errors);
3938
}
4039
returnerrors[0];

‎packages/react-client/src/ReactFlightClient.js‎

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3525,7 +3525,8 @@ function resolveErrorDev(
35253525

35263526
leterror;
35273527
consterrorOptions=
3528-
'cause'inerrorInfo
3528+
// We don't serialize Error.cause in prod so we never need to deserialize
3529+
__DEV__&&'cause'inerrorInfo
35293530
? {
35303531
cause: reviveModel(
35313532
response,
@@ -3536,18 +3537,40 @@ function resolveErrorDev(
35363537
),
35373538
}
35383539
: undefined;
3540+
constisAggregateError=
3541+
typeofAggregateError!=='undefined'&&'errors'inerrorInfo;
3542+
constrevivedErrors=
3543+
// We don't serialize AggregateError.errors in prod so we never need to deserialize
3544+
__DEV__&&isAggregateError
3545+
? reviveModel(
3546+
response,
3547+
// $FlowFixMe[incompatible-cast]
3548+
(errorInfo.errors: JSONValue),
3549+
errorInfo,
3550+
'errors',
3551+
)
3552+
: null;
35393553
constcallStack=buildFakeCallStack(
35403554
response,
35413555
stack,
35423556
env,
35433557
false,
3544-
// $FlowFixMe[incompatible-use]
3545-
Error.bind(
3546-
null,
3547-
message||
3548-
'An error occurred in the Server Components render but no message was provided',
3549-
errorOptions,
3550-
),
3558+
isAggregateError
3559+
? // $FlowFixMe[incompatible-use]
3560+
AggregateError.bind(
3561+
null,
3562+
revivedErrors,
3563+
message||
3564+
'An error occurred in the Server Components render but no message was provided',
3565+
errorOptions,
3566+
)
3567+
: // $FlowFixMe[incompatible-use]
3568+
Error.bind(
3569+
null,
3570+
message||
3571+
'An error occurred in the Server Components render but no message was provided',
3572+
errorOptions,
3573+
),
35513574
);
35523575

35533576
letownerTask: null|ConsoleTask=null;

‎packages/react-client/src/__tests__/ReactFlight-test.js‎

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,204 @@ describe('ReactFlight', () => {
840840
}
841841
});
842842

843+
it('can transport AggregateError',async()=>{
844+
functionrenderError(error){
845+
if(!(errorinstanceofError)){
846+
return`${JSON.stringify(error)}`;
847+
}
848+
letresult=`
849+
is error: ${errorinstanceofAggregateError ? 'AggregateError' : 'Error'}
850+
name: ${error.name}
851+
message: ${error.message}
852+
stack: ${normalizeCodeLocInfo(error.stack).split('\n').slice(0,2).join('\n')}
853+
environmentName: ${error.environmentName}
854+
cause: ${'cause'inerror ? renderError(error.cause) : 'no cause'}`;
855+
if('errors'inerror){
856+
result+=`
857+
errors: [${error.errors.map(e=>renderError(e)).join(',\n')}]`;
858+
}
859+
returnresult;
860+
}
861+
functionComponentClient({error}){
862+
returnrenderError(error);
863+
}
864+
constComponent=clientReference(ComponentClient);
865+
866+
functionServerComponent(){
867+
consterror1=newTypeError('first error');
868+
consterror2=newRangeError('second error');
869+
consterror=newAggregateError([error1,error2],'aggregate');
870+
return<Componenterror={error}/>;
871+
}
872+
873+
consttransport=ReactNoopFlightServer.render(<ServerComponent/>,{
874+
onError(x){
875+
if(__DEV__){
876+
return'a dev digest';
877+
}
878+
return`digest("${x.message}")`;
879+
},
880+
});
881+
882+
awaitact(()=>{
883+
ReactNoop.render(ReactNoopFlightClient.read(transport));
884+
});
885+
886+
if(__DEV__){
887+
expect(ReactNoop).toMatchRenderedOutput(`
888+
is error: AggregateError
889+
name: AggregateError
890+
message: aggregate
891+
stack: AggregateError: aggregate
892+
in ServerComponent (at **)
893+
environmentName: Server
894+
cause: no cause
895+
errors: [
896+
is error: Error
897+
name: TypeError
898+
message: first error
899+
stack: TypeError: first error
900+
in ServerComponent (at **)
901+
environmentName: Server
902+
cause: no cause,
903+
904+
is error: Error
905+
name: RangeError
906+
message: second error
907+
stack: RangeError: second error
908+
in ServerComponent (at **)
909+
environmentName: Server
910+
cause: no cause]`);
911+
}else{
912+
expect(ReactNoop).toMatchRenderedOutput(`
913+
is error: Error
914+
name: Error
915+
message: 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.
916+
stack: Error: 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.
917+
environmentName: undefined
918+
cause: no cause`);
919+
}
920+
});
921+
922+
it('includes AggregateError.errors in thrown errors',async()=>{
923+
functionrenderError(error){
924+
if(!(errorinstanceofError)){
925+
return`${JSON.stringify(error)}`;
926+
}
927+
letresult=`
928+
is error: ${errorinstanceofAggregateError ? 'AggregateError' : 'Error'}
929+
name: ${error.name}
930+
message: ${error.message}
931+
stack: ${normalizeCodeLocInfo(error.stack).split('\n').slice(0,2).join('\n')}
932+
environmentName: ${error.environmentName}
933+
cause: ${'cause'inerror ? renderError(error.cause) : 'no cause'}`;
934+
if('errors'inerror){
935+
result+=`
936+
errors: [${error.errors.map(e=>renderError(e)).join(',\n')}]`;
937+
}
938+
returnresult;
939+
}
940+
941+
functionServerComponent(){
942+
consterror1=newTypeError('first error');
943+
consterror2=newRangeError('second error');
944+
consterror3=newError('third error');
945+
consterror4=newError('fourth error');
946+
consterror5=newError('fifth error');
947+
consterror6=newError('sixth error');
948+
consterror=newAggregateError(
949+
[error1,error2,error3,error4,error5,error6],
950+
'aggregate',
951+
);
952+
throwerror;
953+
}
954+
955+
consttransport=ReactNoopFlightServer.render(<ServerComponent/>,{
956+
onError(x){
957+
if(__DEV__){
958+
return'a dev digest';
959+
}
960+
return`digest("${x.message}")`;
961+
},
962+
});
963+
964+
leterror;
965+
try{
966+
awaitact(()=>{
967+
ReactNoop.render(ReactNoopFlightClient.read(transport));
968+
});
969+
}catch(x){
970+
error=x;
971+
}
972+
973+
if(__DEV__){
974+
expect(renderError(error)).toEqual(`
975+
is error: AggregateError
976+
name: AggregateError
977+
message: aggregate
978+
stack: AggregateError: aggregate
979+
in ServerComponent (at **)
980+
environmentName: Server
981+
cause: no cause
982+
errors: [
983+
is error: Error
984+
name: TypeError
985+
message: first error
986+
stack: TypeError: first error
987+
in ServerComponent (at **)
988+
environmentName: Server
989+
cause: no cause,
990+
991+
is error: Error
992+
name: RangeError
993+
message: second error
994+
stack: RangeError: second error
995+
in ServerComponent (at **)
996+
environmentName: Server
997+
cause: no cause,
998+
999+
is error: Error
1000+
name: Error
1001+
message: third error
1002+
stack: Error: third error
1003+
in ServerComponent (at **)
1004+
environmentName: Server
1005+
cause: no cause,
1006+
1007+
is error: Error
1008+
name: Error
1009+
message: fourth error
1010+
stack: Error: fourth error
1011+
in ServerComponent (at **)
1012+
environmentName: Server
1013+
cause: no cause,
1014+
1015+
is error: Error
1016+
name: Error
1017+
message: fifth error
1018+
stack: Error: fifth error
1019+
in ServerComponent (at **)
1020+
environmentName: Server
1021+
cause: no cause,
1022+
1023+
is error: Error
1024+
name: Error
1025+
message: sixth error
1026+
stack: Error: sixth error
1027+
in ServerComponent (at **)
1028+
environmentName: Server
1029+
cause: no cause]`);
1030+
}else{
1031+
expect(renderError(error)).toEqual(`
1032+
is error: Error
1033+
name: Error
1034+
message: 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.
1035+
stack: Error: 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.
1036+
environmentName: undefined
1037+
cause: no cause`);
1038+
}
1039+
});
1040+
8431041
it('can transport cyclic objects',async()=>{
8441042
functionComponentClient({prop}){
8451043
expect(prop.obj.obj.obj).toBe(prop.obj.obj);

‎packages/react-dom/src/__tests__/ReactDOMSelect-test.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1485,7 +1485,6 @@ describe('ReactDOMSelect', () => {
14851485
);
14861486
}),
14871487
).rejects.toThrowError(
1488-
// eslint-disable-next-line no-undef
14891488
newAggregateError([
14901489
newTypeError('prod message'),
14911490
newTypeError('prod message'),

‎packages/react-reconciler/src/__tests__/ReactFlushSync-test.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,6 @@ describe('ReactFlushSync', () => {
337337
expect(getVisibleChildren(container3)).toEqual('aww');
338338

339339
// Because there were multiple errors, React threw an AggregateError.
340-
// eslint-disable-next-line no-undef
341340
expect(error).toBeInstanceOf(AggregateError);
342341
expect(error.errors.length).toBe(2);
343342
expect(error.errors[0]).toBe(aahh);

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4169,6 +4169,14 @@ function serializeErrorValue(request: Request, error: Error): string {
41694169
constcauseId=outlineModel(request,cause);
41704170
errorInfo.cause=serializeByValueID(causeId);
41714171
}
4172+
if(
4173+
typeofAggregateError!=='undefined'&&
4174+
errorinstanceofAggregateError
4175+
){
4176+
consterrors: ReactClientValue=(error.errors: any);
4177+
consterrorsId=outlineModel(request,errors);
4178+
errorInfo.errors=serializeByValueID(errorsId);
4179+
}
41724180
constid=outlineModel(request,errorInfo);
41734181
return'$Z'+id.toString(16);
41744182
}else{
@@ -4211,6 +4219,15 @@ function serializeDebugErrorValue(
42114219
constcauseId=outlineDebugModel(request,counter,cause);
42124220
errorInfo.cause=serializeByValueID(causeId);
42134221
}
4222+
if(
4223+
typeofAggregateError!=='undefined'&&
4224+
errorinstanceofAggregateError
4225+
){
4226+
counter.objectLimit--;
4227+
consterrors: ReactClientValue=(error.errors: any);
4228+
consterrorsId=outlineDebugModel(request,counter,errors);
4229+
errorInfo.errors=serializeByValueID(errorsId);
4230+
}
42144231
constid=outlineDebugModel(
42154232
request,
42164233
{objectLimit: stack.length*2+1},
@@ -4240,6 +4257,7 @@ function emitErrorChunk(
42404257
letstack: ReactStackTrace;
42414258
letenv=(0,request.environmentName)();
42424259
letcauseReference: null|string=null;
4260+
leterrorsReference: null|string=null;
42434261
try{
42444262
if(errorinstanceofError){
42454263
name=error.name;
@@ -4259,6 +4277,16 @@ function emitErrorChunk(
42594277
: outlineModel(request,cause);
42604278
causeReference=serializeByValueID(causeId);
42614279
}
4280+
if(
4281+
typeofAggregateError!=='undefined'&&
4282+
errorinstanceofAggregateError
4283+
){
4284+
consterrors: ReactClientValue=(error.errors: any);
4285+
consterrorsId=debug
4286+
? outlineDebugModel(request,{objectLimit: 5},errors)
4287+
: outlineModel(request,errors);
4288+
errorsReference=serializeByValueID(errorsId);
4289+
}
42624290
}elseif(typeoferror==='object'&&error!==null){
42634291
message=describeObjectForErrorMessage(error);
42644292
stack=[];
@@ -4277,6 +4305,9 @@ function emitErrorChunk(
42774305
if(causeReference!==null){
42784306
(errorInfo: ReactErrorInfoDev).cause=causeReference;
42794307
}
4308+
if(errorsReference!==null){
4309+
(errorInfo: ReactErrorInfoDev).errors=errorsReference;
4310+
}
42804311
}else{
42814312
errorInfo ={digest};
42824313
}

‎packages/react/src/ReactAct.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ let didWarnNoAwaitAct = false;
2323

2424
functionaggregateErrors(errors: Array<mixed>): mixed{
2525
if(errors.length>1&&typeofAggregateError==='function'){
26-
// eslint-disable-next-line no-undef
2726
returnnewAggregateError(errors);
2827
}
2928
returnerrors[0];

‎packages/shared/ReactTypes.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ export type ReactErrorInfoDev = {
244244
+env: string,
245245
+owner?: null|string,
246246
cause?: JSONValue,
247+
errors?: JSONValue,
247248
};
248249

249250
export type ReactErrorInfo = ReactErrorInfoProd | ReactErrorInfoDev;

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 74568e8

Browse files
authored
[Flight] Transport AggregateErrors.errors (#36156)
1 parent 9627b5a commit 74568e8

10 files changed

Lines changed: 262 additions & 13 deletions

File tree

‎.eslintrc.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,7 @@ module.exports = {
566566
CallSite: 'readonly',
567567
ConsoleTask: 'readonly',// TOOD: Figure out what the official name of this will be.
568568
ReturnType: 'readonly',
569+
AggregateError: 'readonly',
569570
AnimationFrameID: 'readonly',
570571
WeakRef: 'readonly',
571572
// For Flow type annotation. Only `BigInt` is valid at runtime.

‎packages/internal-test-utils/ReactInternalTestUtils.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ ${diff(expectedLog, actualLog)}
122122

123123
functionaggregateErrors(errors: Array<mixed>): mixed{
124124
if(errors.length>1&&typeofAggregateError==='function'){
125-
// eslint-disable-next-line no-undef
126125
returnnewAggregateError(errors);
127126
}
128127
returnerrors[0];

‎packages/internal-test-utils/internalAct.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ async function waitForMicrotasks() {
3434

3535
functionaggregateErrors(errors: Array<mixed>): mixed{
3636
if(errors.length>1&&typeofAggregateError==='function'){
37-
// eslint-disable-next-line no-undef
3837
returnnewAggregateError(errors);
3938
}
4039
returnerrors[0];

‎packages/react-client/src/ReactFlightClient.js‎

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3525,7 +3525,8 @@ function resolveErrorDev(
35253525

35263526
leterror;
35273527
consterrorOptions=
3528-
'cause'inerrorInfo
3528+
// We don't serialize Error.cause in prod so we never need to deserialize
3529+
__DEV__&&'cause'inerrorInfo
35293530
? {
35303531
cause: reviveModel(
35313532
response,
@@ -3536,18 +3537,40 @@ function resolveErrorDev(
35363537
),
35373538
}
35383539
: undefined;
3540+
constisAggregateError=
3541+
typeofAggregateError!=='undefined'&&'errors'inerrorInfo;
3542+
constrevivedErrors=
3543+
// We don't serialize AggregateError.errors in prod so we never need to deserialize
3544+
__DEV__&&isAggregateError
3545+
? reviveModel(
3546+
response,
3547+
// $FlowFixMe[incompatible-cast]
3548+
(errorInfo.errors: JSONValue),
3549+
errorInfo,
3550+
'errors',
3551+
)
3552+
: null;
35393553
constcallStack=buildFakeCallStack(
35403554
response,
35413555
stack,
35423556
env,
35433557
false,
3544-
// $FlowFixMe[incompatible-use]
3545-
Error.bind(
3546-
null,
3547-
message||
3548-
'An error occurred in the Server Components render but no message was provided',
3549-
errorOptions,
3550-
),
3558+
isAggregateError
3559+
? // $FlowFixMe[incompatible-use]
3560+
AggregateError.bind(
3561+
null,
3562+
revivedErrors,
3563+
message||
3564+
'An error occurred in the Server Components render but no message was provided',
3565+
errorOptions,
3566+
)
3567+
: // $FlowFixMe[incompatible-use]
3568+
Error.bind(
3569+
null,
3570+
message||
3571+
'An error occurred in the Server Components render but no message was provided',
3572+
errorOptions,
3573+
),
35513574
);
35523575

35533576
letownerTask: null|ConsoleTask=null;

‎packages/react-client/src/__tests__/ReactFlight-test.js‎

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,204 @@ describe('ReactFlight', () => {
840840
}
841841
});
842842

843+
it('can transport AggregateError',async()=>{
844+
functionrenderError(error){
845+
if(!(errorinstanceofError)){
846+
return`${JSON.stringify(error)}`;
847+
}
848+
letresult=`
849+
is error: ${errorinstanceofAggregateError ? 'AggregateError' : 'Error'}
850+
name: ${error.name}
851+
message: ${error.message}
852+
stack: ${normalizeCodeLocInfo(error.stack).split('\n').slice(0,2).join('\n')}
853+
environmentName: ${error.environmentName}
854+
cause: ${'cause'inerror ? renderError(error.cause) : 'no cause'}`;
855+
if('errors'inerror){
856+
result+=`
857+
errors: [${error.errors.map(e=>renderError(e)).join(',\n')}]`;
858+
}
859+
returnresult;
860+
}
861+
functionComponentClient({error}){
862+
returnrenderError(error);
863+
}
864+
constComponent=clientReference(ComponentClient);
865+
866+
functionServerComponent(){
867+
consterror1=newTypeError('first error');
868+
consterror2=newRangeError('second error');
869+
consterror=newAggregateError([error1,error2],'aggregate');
870+
return<Componenterror={error}/>;
871+
}
872+
873+
consttransport=ReactNoopFlightServer.render(<ServerComponent/>,{
874+
onError(x){
875+
if(__DEV__){
876+
return'a dev digest';
877+
}
878+
return`digest("${x.message}")`;
879+
},
880+
});
881+
882+
awaitact(()=>{
883+
ReactNoop.render(ReactNoopFlightClient.read(transport));
884+
});
885+
886+
if(__DEV__){
887+
expect(ReactNoop).toMatchRenderedOutput(`
888+
is error: AggregateError
889+
name: AggregateError
890+
message: aggregate
891+
stack: AggregateError: aggregate
892+
in ServerComponent (at **)
893+
environmentName: Server
894+
cause: no cause
895+
errors: [
896+
is error: Error
897+
name: TypeError
898+
message: first error
899+
stack: TypeError: first error
900+
in ServerComponent (at **)
901+
environmentName: Server
902+
cause: no cause,
903+
904+
is error: Error
905+
name: RangeError
906+
message: second error
907+
stack: RangeError: second error
908+
in ServerComponent (at **)
909+
environmentName: Server
910+
cause: no cause]`);
911+
}else{
912+
expect(ReactNoop).toMatchRenderedOutput(`
913+
is error: Error
914+
name: Error
915+
message: 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.
916+
stack: Error: 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.
917+
environmentName: undefined
918+
cause: no cause`);
919+
}
920+
});
921+
922+
it('includes AggregateError.errors in thrown errors',async()=>{
923+
functionrenderError(error){
924+
if(!(errorinstanceofError)){
925+
return`${JSON.stringify(error)}`;
926+
}
927+
letresult=`
928+
is error: ${errorinstanceofAggregateError ? 'AggregateError' : 'Error'}
929+
name: ${error.name}
930+
message: ${error.message}
931+
stack: ${normalizeCodeLocInfo(error.stack).split('\n').slice(0,2).join('\n')}
932+
environmentName: ${error.environmentName}
933+
cause: ${'cause'inerror ? renderError(error.cause) : 'no cause'}`;
934+
if('errors'inerror){
935+
result+=`
936+
errors: [${error.errors.map(e=>renderError(e)).join(',\n')}]`;
937+
}
938+
returnresult;
939+
}
940+
941+
functionServerComponent(){
942+
consterror1=newTypeError('first error');
943+
consterror2=newRangeError('second error');
944+
consterror3=newError('third error');
945+
consterror4=newError('fourth error');
946+
consterror5=newError('fifth error');
947+
consterror6=newError('sixth error');
948+
consterror=newAggregateError(
949+
[error1,error2,error3,error4,error5,error6],
950+
'aggregate',
951+
);
952+
throwerror;
953+
}
954+
955+
consttransport=ReactNoopFlightServer.render(<ServerComponent/>,{
956+
onError(x){
957+
if(__DEV__){
958+
return'a dev digest';
959+
}
960+
return`digest("${x.message}")`;
961+
},
962+
});
963+
964+
leterror;
965+
try{
966+
awaitact(()=>{
967+
ReactNoop.render(ReactNoopFlightClient.read(transport));
968+
});
969+
}catch(x){
970+
error=x;
971+
}
972+
973+
if(__DEV__){
974+
expect(renderError(error)).toEqual(`
975+
is error: AggregateError
976+
name: AggregateError
977+
message: aggregate
978+
stack: AggregateError: aggregate
979+
in ServerComponent (at **)
980+
environmentName: Server
981+
cause: no cause
982+
errors: [
983+
is error: Error
984+
name: TypeError
985+
message: first error
986+
stack: TypeError: first error
987+
in ServerComponent (at **)
988+
environmentName: Server
989+
cause: no cause,
990+
991+
is error: Error
992+
name: RangeError
993+
message: second error
994+
stack: RangeError: second error
995+
in ServerComponent (at **)
996+
environmentName: Server
997+
cause: no cause,
998+
999+
is error: Error
1000+
name: Error
1001+
message: third error
1002+
stack: Error: third error
1003+
in ServerComponent (at **)
1004+
environmentName: Server
1005+
cause: no cause,
1006+
1007+
is error: Error
1008+
name: Error
1009+
message: fourth error
1010+
stack: Error: fourth error
1011+
in ServerComponent (at **)
1012+
environmentName: Server
1013+
cause: no cause,
1014+
1015+
is error: Error
1016+
name: Error
1017+
message: fifth error
1018+
stack: Error: fifth error
1019+
in ServerComponent (at **)
1020+
environmentName: Server
1021+
cause: no cause,
1022+
1023+
is error: Error
1024+
name: Error
1025+
message: sixth error
1026+
stack: Error: sixth error
1027+
in ServerComponent (at **)
1028+
environmentName: Server
1029+
cause: no cause]`);
1030+
}else{
1031+
expect(renderError(error)).toEqual(`
1032+
is error: Error
1033+
name: Error
1034+
message: 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.
1035+
stack: Error: 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.
1036+
environmentName: undefined
1037+
cause: no cause`);
1038+
}
1039+
});
1040+
8431041
it('can transport cyclic objects',async()=>{
8441042
functionComponentClient({prop}){
8451043
expect(prop.obj.obj.obj).toBe(prop.obj.obj);

‎packages/react-dom/src/__tests__/ReactDOMSelect-test.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1485,7 +1485,6 @@ describe('ReactDOMSelect', () => {
14851485
);
14861486
}),
14871487
).rejects.toThrowError(
1488-
// eslint-disable-next-line no-undef
14891488
newAggregateError([
14901489
newTypeError('prod message'),
14911490
newTypeError('prod message'),

‎packages/react-reconciler/src/__tests__/ReactFlushSync-test.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,6 @@ describe('ReactFlushSync', () => {
337337
expect(getVisibleChildren(container3)).toEqual('aww');
338338

339339
// Because there were multiple errors, React threw an AggregateError.
340-
// eslint-disable-next-line no-undef
341340
expect(error).toBeInstanceOf(AggregateError);
342341
expect(error.errors.length).toBe(2);
343342
expect(error.errors[0]).toBe(aahh);

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4169,6 +4169,14 @@ function serializeErrorValue(request: Request, error: Error): string {
41694169
constcauseId=outlineModel(request,cause);
41704170
errorInfo.cause=serializeByValueID(causeId);
41714171
}
4172+
if(
4173+
typeofAggregateError!=='undefined'&&
4174+
errorinstanceofAggregateError
4175+
){
4176+
consterrors: ReactClientValue=(error.errors: any);
4177+
consterrorsId=outlineModel(request,errors);
4178+
errorInfo.errors=serializeByValueID(errorsId);
4179+
}
41724180
constid=outlineModel(request,errorInfo);
41734181
return'$Z'+id.toString(16);
41744182
}else{
@@ -4211,6 +4219,15 @@ function serializeDebugErrorValue(
42114219
constcauseId=outlineDebugModel(request,counter,cause);
42124220
errorInfo.cause=serializeByValueID(causeId);
42134221
}
4222+
if(
4223+
typeofAggregateError!=='undefined'&&
4224+
errorinstanceofAggregateError
4225+
){
4226+
counter.objectLimit--;
4227+
consterrors: ReactClientValue=(error.errors: any);
4228+
consterrorsId=outlineDebugModel(request,counter,errors);
4229+
errorInfo.errors=serializeByValueID(errorsId);
4230+
}
42144231
constid=outlineDebugModel(
42154232
request,
42164233
{objectLimit: stack.length*2+1},
@@ -4240,6 +4257,7 @@ function emitErrorChunk(
42404257
letstack: ReactStackTrace;
42414258
letenv=(0,request.environmentName)();
42424259
letcauseReference: null|string=null;
4260+
leterrorsReference: null|string=null;
42434261
try{
42444262
if(errorinstanceofError){
42454263
name=error.name;
@@ -4259,6 +4277,16 @@ function emitErrorChunk(
42594277
: outlineModel(request,cause);
42604278
causeReference=serializeByValueID(causeId);
42614279
}
4280+
if(
4281+
typeofAggregateError!=='undefined'&&
4282+
errorinstanceofAggregateError
4283+
){
4284+
consterrors: ReactClientValue=(error.errors: any);
4285+
consterrorsId=debug
4286+
? outlineDebugModel(request,{objectLimit: 5},errors)
4287+
: outlineModel(request,errors);
4288+
errorsReference=serializeByValueID(errorsId);
4289+
}
42624290
}elseif(typeoferror==='object'&&error!==null){
42634291
message=describeObjectForErrorMessage(error);
42644292
stack=[];
@@ -4277,6 +4305,9 @@ function emitErrorChunk(
42774305
if(causeReference!==null){
42784306
(errorInfo: ReactErrorInfoDev).cause=causeReference;
42794307
}
4308+
if(errorsReference!==null){
4309+
(errorInfo: ReactErrorInfoDev).errors=errorsReference;
4310+
}
42804311
}else{
42814312
errorInfo ={digest};
42824313
}

‎packages/react/src/ReactAct.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ let didWarnNoAwaitAct = false;
2323

2424
functionaggregateErrors(errors: Array<mixed>): mixed{
2525
if(errors.length>1&&typeofAggregateError==='function'){
26-
// eslint-disable-next-line no-undef
2726
returnnewAggregateError(errors);
2827
}
2928
returnerrors[0];

‎packages/shared/ReactTypes.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ export type ReactErrorInfoDev = {
244244
+env: string,
245245
+owner?: null|string,
246246
cause?: JSONValue,
247+
errors?: JSONValue,
247248
};
248249

249250
export type ReactErrorInfo = ReactErrorInfoProd | ReactErrorInfoDev;

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Commit 74568e8

Browse files
authored
[Flight] Transport AggregateErrors.errors (#36156)
1 parent 9627b5a commit 74568e8

10 files changed

Lines changed: 262 additions & 13 deletions

File tree

‎.eslintrc.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,7 @@ module.exports = {
566566
CallSite: 'readonly',
567567
ConsoleTask: 'readonly',// TOOD: Figure out what the official name of this will be.
568568
ReturnType: 'readonly',
569+
AggregateError: 'readonly',
569570
AnimationFrameID: 'readonly',
570571
WeakRef: 'readonly',
571572
// For Flow type annotation. Only `BigInt` is valid at runtime.

‎packages/internal-test-utils/ReactInternalTestUtils.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ ${diff(expectedLog, actualLog)}
122122

123123
functionaggregateErrors(errors: Array<mixed>): mixed{
124124
if(errors.length>1&&typeofAggregateError==='function'){
125-
// eslint-disable-next-line no-undef
126125
returnnewAggregateError(errors);
127126
}
128127
returnerrors[0];

‎packages/internal-test-utils/internalAct.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ async function waitForMicrotasks() {
3434

3535
functionaggregateErrors(errors: Array<mixed>): mixed{
3636
if(errors.length>1&&typeofAggregateError==='function'){
37-
// eslint-disable-next-line no-undef
3837
returnnewAggregateError(errors);
3938
}
4039
returnerrors[0];

‎packages/react-client/src/ReactFlightClient.js‎

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3525,7 +3525,8 @@ function resolveErrorDev(
35253525

35263526
leterror;
35273527
consterrorOptions=
3528-
'cause'inerrorInfo
3528+
// We don't serialize Error.cause in prod so we never need to deserialize
3529+
__DEV__&&'cause'inerrorInfo
35293530
? {
35303531
cause: reviveModel(
35313532
response,
@@ -3536,18 +3537,40 @@ function resolveErrorDev(
35363537
),
35373538
}
35383539
: undefined;
3540+
constisAggregateError=
3541+
typeofAggregateError!=='undefined'&&'errors'inerrorInfo;
3542+
constrevivedErrors=
3543+
// We don't serialize AggregateError.errors in prod so we never need to deserialize
3544+
__DEV__&&isAggregateError
3545+
? reviveModel(
3546+
response,
3547+
// $FlowFixMe[incompatible-cast]
3548+
(errorInfo.errors: JSONValue),
3549+
errorInfo,
3550+
'errors',
3551+
)
3552+
: null;
35393553
constcallStack=buildFakeCallStack(
35403554
response,
35413555
stack,
35423556
env,
35433557
false,
3544-
// $FlowFixMe[incompatible-use]
3545-
Error.bind(
3546-
null,
3547-
message||
3548-
'An error occurred in the Server Components render but no message was provided',
3549-
errorOptions,
3550-
),
3558+
isAggregateError
3559+
? // $FlowFixMe[incompatible-use]
3560+
AggregateError.bind(
3561+
null,
3562+
revivedErrors,
3563+
message||
3564+
'An error occurred in the Server Components render but no message was provided',
3565+
errorOptions,
3566+
)
3567+
: // $FlowFixMe[incompatible-use]
3568+
Error.bind(
3569+
null,
3570+
message||
3571+
'An error occurred in the Server Components render but no message was provided',
3572+
errorOptions,
3573+
),
35513574
);
35523575

35533576
letownerTask: null|ConsoleTask=null;

‎packages/react-client/src/__tests__/ReactFlight-test.js‎

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,204 @@ describe('ReactFlight', () => {
840840
}
841841
});
842842

843+
it('can transport AggregateError',async()=>{
844+
functionrenderError(error){
845+
if(!(errorinstanceofError)){
846+
return`${JSON.stringify(error)}`;
847+
}
848+
letresult=`
849+
is error: ${errorinstanceofAggregateError ? 'AggregateError' : 'Error'}
850+
name: ${error.name}
851+
message: ${error.message}
852+
stack: ${normalizeCodeLocInfo(error.stack).split('\n').slice(0,2).join('\n')}
853+
environmentName: ${error.environmentName}
854+
cause: ${'cause'inerror ? renderError(error.cause) : 'no cause'}`;
855+
if('errors'inerror){
856+
result+=`
857+
errors: [${error.errors.map(e=>renderError(e)).join(',\n')}]`;
858+
}
859+
returnresult;
860+
}
861+
functionComponentClient({error}){
862+
returnrenderError(error);
863+
}
864+
constComponent=clientReference(ComponentClient);
865+
866+
functionServerComponent(){
867+
consterror1=newTypeError('first error');
868+
consterror2=newRangeError('second error');
869+
consterror=newAggregateError([error1,error2],'aggregate');
870+
return<Componenterror={error}/>;
871+
}
872+
873+
consttransport=ReactNoopFlightServer.render(<ServerComponent/>,{
874+
onError(x){
875+
if(__DEV__){
876+
return'a dev digest';
877+
}
878+
return`digest("${x.message}")`;
879+
},
880+
});
881+
882+
awaitact(()=>{
883+
ReactNoop.render(ReactNoopFlightClient.read(transport));
884+
});
885+
886+
if(__DEV__){
887+
expect(ReactNoop).toMatchRenderedOutput(`
888+
is error: AggregateError
889+
name: AggregateError
890+
message: aggregate
891+
stack: AggregateError: aggregate
892+
in ServerComponent (at **)
893+
environmentName: Server
894+
cause: no cause
895+
errors: [
896+
is error: Error
897+
name: TypeError
898+
message: first error
899+
stack: TypeError: first error
900+
in ServerComponent (at **)
901+
environmentName: Server
902+
cause: no cause,
903+
904+
is error: Error
905+
name: RangeError
906+
message: second error
907+
stack: RangeError: second error
908+
in ServerComponent (at **)
909+
environmentName: Server
910+
cause: no cause]`);
911+
}else{
912+
expect(ReactNoop).toMatchRenderedOutput(`
913+
is error: Error
914+
name: Error
915+
message: 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.
916+
stack: Error: 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.
917+
environmentName: undefined
918+
cause: no cause`);
919+
}
920+
});
921+
922+
it('includes AggregateError.errors in thrown errors',async()=>{
923+
functionrenderError(error){
924+
if(!(errorinstanceofError)){
925+
return`${JSON.stringify(error)}`;
926+
}
927+
letresult=`
928+
is error: ${errorinstanceofAggregateError ? 'AggregateError' : 'Error'}
929+
name: ${error.name}
930+
message: ${error.message}
931+
stack: ${normalizeCodeLocInfo(error.stack).split('\n').slice(0,2).join('\n')}
932+
environmentName: ${error.environmentName}
933+
cause: ${'cause'inerror ? renderError(error.cause) : 'no cause'}`;
934+
if('errors'inerror){
935+
result+=`
936+
errors: [${error.errors.map(e=>renderError(e)).join(',\n')}]`;
937+
}
938+
returnresult;
939+
}
940+
941+
functionServerComponent(){
942+
consterror1=newTypeError('first error');
943+
consterror2=newRangeError('second error');
944+
consterror3=newError('third error');
945+
consterror4=newError('fourth error');
946+
consterror5=newError('fifth error');
947+
consterror6=newError('sixth error');
948+
consterror=newAggregateError(
949+
[error1,error2,error3,error4,error5,error6],
950+
'aggregate',
951+
);
952+
throwerror;
953+
}
954+
955+
consttransport=ReactNoopFlightServer.render(<ServerComponent/>,{
956+
onError(x){
957+
if(__DEV__){
958+
return'a dev digest';
959+
}
960+
return`digest("${x.message}")`;
961+
},
962+
});
963+
964+
leterror;
965+
try{
966+
awaitact(()=>{
967+
ReactNoop.render(ReactNoopFlightClient.read(transport));
968+
});
969+
}catch(x){
970+
error=x;
971+
}
972+
973+
if(__DEV__){
974+
expect(renderError(error)).toEqual(`
975+
is error: AggregateError
976+
name: AggregateError
977+
message: aggregate
978+
stack: AggregateError: aggregate
979+
in ServerComponent (at **)
980+
environmentName: Server
981+
cause: no cause
982+
errors: [
983+
is error: Error
984+
name: TypeError
985+
message: first error
986+
stack: TypeError: first error
987+
in ServerComponent (at **)
988+
environmentName: Server
989+
cause: no cause,
990+
991+
is error: Error
992+
name: RangeError
993+
message: second error
994+
stack: RangeError: second error
995+
in ServerComponent (at **)
996+
environmentName: Server
997+
cause: no cause,
998+
999+
is error: Error
1000+
name: Error
1001+
message: third error
1002+
stack: Error: third error
1003+
in ServerComponent (at **)
1004+
environmentName: Server
1005+
cause: no cause,
1006+
1007+
is error: Error
1008+
name: Error
1009+
message: fourth error
1010+
stack: Error: fourth error
1011+
in ServerComponent (at **)
1012+
environmentName: Server
1013+
cause: no cause,
1014+
1015+
is error: Error
1016+
name: Error
1017+
message: fifth error
1018+
stack: Error: fifth error
1019+
in ServerComponent (at **)
1020+
environmentName: Server
1021+
cause: no cause,
1022+
1023+
is error: Error
1024+
name: Error
1025+
message: sixth error
1026+
stack: Error: sixth error
1027+
in ServerComponent (at **)
1028+
environmentName: Server
1029+
cause: no cause]`);
1030+
}else{
1031+
expect(renderError(error)).toEqual(`
1032+
is error: Error
1033+
name: Error
1034+
message: 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.
1035+
stack: Error: 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.
1036+
environmentName: undefined
1037+
cause: no cause`);
1038+
}
1039+
});
1040+
8431041
it('can transport cyclic objects',async()=>{
8441042
functionComponentClient({prop}){
8451043
expect(prop.obj.obj.obj).toBe(prop.obj.obj);

‎packages/react-dom/src/__tests__/ReactDOMSelect-test.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1485,7 +1485,6 @@ describe('ReactDOMSelect', () => {
14851485
);
14861486
}),
14871487
).rejects.toThrowError(
1488-
// eslint-disable-next-line no-undef
14891488
newAggregateError([
14901489
newTypeError('prod message'),
14911490
newTypeError('prod message'),

‎packages/react-reconciler/src/__tests__/ReactFlushSync-test.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,6 @@ describe('ReactFlushSync', () => {
337337
expect(getVisibleChildren(container3)).toEqual('aww');
338338

339339
// Because there were multiple errors, React threw an AggregateError.
340-
// eslint-disable-next-line no-undef
341340
expect(error).toBeInstanceOf(AggregateError);
342341
expect(error.errors.length).toBe(2);
343342
expect(error.errors[0]).toBe(aahh);

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4169,6 +4169,14 @@ function serializeErrorValue(request: Request, error: Error): string {
41694169
constcauseId=outlineModel(request,cause);
41704170
errorInfo.cause=serializeByValueID(causeId);
41714171
}
4172+
if(
4173+
typeofAggregateError!=='undefined'&&
4174+
errorinstanceofAggregateError
4175+
){
4176+
consterrors: ReactClientValue=(error.errors: any);
4177+
consterrorsId=outlineModel(request,errors);
4178+
errorInfo.errors=serializeByValueID(errorsId);
4179+
}
41724180
constid=outlineModel(request,errorInfo);
41734181
return'$Z'+id.toString(16);
41744182
}else{
@@ -4211,6 +4219,15 @@ function serializeDebugErrorValue(
42114219
constcauseId=outlineDebugModel(request,counter,cause);
42124220
errorInfo.cause=serializeByValueID(causeId);
42134221
}
4222+
if(
4223+
typeofAggregateError!=='undefined'&&
4224+
errorinstanceofAggregateError
4225+
){
4226+
counter.objectLimit--;
4227+
consterrors: ReactClientValue=(error.errors: any);
4228+
consterrorsId=outlineDebugModel(request,counter,errors);
4229+
errorInfo.errors=serializeByValueID(errorsId);
4230+
}
42144231
constid=outlineDebugModel(
42154232
request,
42164233
{objectLimit: stack.length*2+1},
@@ -4240,6 +4257,7 @@ function emitErrorChunk(
42404257
letstack: ReactStackTrace;
42414258
letenv=(0,request.environmentName)();
42424259
letcauseReference: null|string=null;
4260+
leterrorsReference: null|string=null;
42434261
try{
42444262
if(errorinstanceofError){
42454263
name=error.name;
@@ -4259,6 +4277,16 @@ function emitErrorChunk(
42594277
: outlineModel(request,cause);
42604278
causeReference=serializeByValueID(causeId);
42614279
}
4280+
if(
4281+
typeofAggregateError!=='undefined'&&
4282+
errorinstanceofAggregateError
4283+
){
4284+
consterrors: ReactClientValue=(error.errors: any);
4285+
consterrorsId=debug
4286+
? outlineDebugModel(request,{objectLimit: 5},errors)
4287+
: outlineModel(request,errors);
4288+
errorsReference=serializeByValueID(errorsId);
4289+
}
42624290
}elseif(typeoferror==='object'&&error!==null){
42634291
message=describeObjectForErrorMessage(error);
42644292
stack=[];
@@ -4277,6 +4305,9 @@ function emitErrorChunk(
42774305
if(causeReference!==null){
42784306
(errorInfo: ReactErrorInfoDev).cause=causeReference;
42794307
}
4308+
if(errorsReference!==null){
4309+
(errorInfo: ReactErrorInfoDev).errors=errorsReference;
4310+
}
42804311
}else{
42814312
errorInfo ={digest};
42824313
}

‎packages/react/src/ReactAct.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ let didWarnNoAwaitAct = false;
2323

2424
functionaggregateErrors(errors: Array<mixed>): mixed{
2525
if(errors.length>1&&typeofAggregateError==='function'){
26-
// eslint-disable-next-line no-undef
2726
returnnewAggregateError(errors);
2827
}
2928
returnerrors[0];

‎packages/shared/ReactTypes.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ export type ReactErrorInfoDev = {
244244
+env: string,
245245
+owner?: null|string,
246246
cause?: JSONValue,
247+
errors?: JSONValue,
247248
};
248249

249250
export type ReactErrorInfo = ReactErrorInfoProd | ReactErrorInfoDev;

0 commit comments

Comments
 (0)