Skip to content

Commit 0586306

Browse files
Better handling of deferred promises that resolve/reject with undefined (#10690)
Co-authored-by: Jacob Ebey <[email protected]>
1 parent 4e4c08a commit 0586306

File tree

4 files changed

+84
-3
lines changed

4 files changed

+84
-3
lines changed

.changeset/defer-resolve-undefined.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@remix-run/router": patch
3+
---
4+
5+
Better handling of deferred promises that resolve/reject with `undefined`

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@
109109
},
110110
"filesize": {
111111
"packages/router/dist/router.umd.min.js": {
112-
"none": "46.9 kB"
112+
"none": "47.2 kB"
113113
},
114114
"packages/react-router/dist/react-router.production.min.js": {
115115
"none": "13.8 kB"

packages/router/__tests__/router-test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14310,6 +14310,12 @@ describe("a router", () => {
1431014310
),
1431114311
});
1431214312
}
14313+
if (new URL(request.url).searchParams.has("undefined")) {
14314+
return defer({
14315+
critical: "loader",
14316+
lazy: new Promise((r) => setTimeout(() => r(undefined), 10)),
14317+
});
14318+
}
1431314319
if (new URL(request.url).searchParams.has("status")) {
1431414320
return defer(
1431514321
{
@@ -15114,6 +15120,42 @@ describe("a router", () => {
1511415120
});
1511515121
});
1511615122

15123+
it("should return rejected DeferredData on symbol for resolved undefined", async () => {
15124+
let context = (await query(
15125+
createRequest("/parent/deferred?undefined")
15126+
)) as StaticHandlerContext;
15127+
expect(context).toMatchObject({
15128+
loaderData: {
15129+
parent: "PARENT LOADER",
15130+
deferred: {
15131+
critical: "loader",
15132+
lazy: expect.trackedPromise(),
15133+
},
15134+
},
15135+
activeDeferreds: {
15136+
deferred: expect.deferredData(false),
15137+
},
15138+
});
15139+
await new Promise((r) => setTimeout(r, 10));
15140+
expect(context).toMatchObject({
15141+
loaderData: {
15142+
parent: "PARENT LOADER",
15143+
deferred: {
15144+
critical: "loader",
15145+
lazy: expect.trackedPromise(
15146+
null,
15147+
new Error(
15148+
`Deferred data for key "lazy" resolved/rejected with \`undefined\`, you must resolve/reject with a value or \`null\`.`
15149+
)
15150+
),
15151+
},
15152+
},
15153+
activeDeferreds: {
15154+
deferred: expect.deferredData(true),
15155+
},
15156+
});
15157+
});
15158+
1511715159
it("should return DeferredData on symbol with status + headers", async () => {
1511815160
let context = (await query(
1511915161
createRequest("/parent/deferred?status")
@@ -16179,6 +16221,28 @@ describe("a router", () => {
1617916221
expect(result[UNSAFE_DEFERRED_SYMBOL]).deferredData(true);
1618016222
});
1618116223

16224+
it("should return rejected DeferredData on symbol for resolved undefined", async () => {
16225+
let result = await queryRoute(
16226+
createRequest("/parent/deferred?undefined")
16227+
);
16228+
expect(result).toMatchObject({
16229+
critical: "loader",
16230+
lazy: expect.trackedPromise(),
16231+
});
16232+
expect(result[UNSAFE_DEFERRED_SYMBOL]).deferredData(false);
16233+
await new Promise((r) => setTimeout(r, 10));
16234+
expect(result).toMatchObject({
16235+
critical: "loader",
16236+
lazy: expect.trackedPromise(
16237+
null,
16238+
new Error(
16239+
`Deferred data for key "lazy" resolved/rejected with \`undefined\`, you must resolve/reject with a value or \`null\`.`
16240+
)
16241+
),
16242+
});
16243+
expect(result[UNSAFE_DEFERRED_SYMBOL]).deferredData(true);
16244+
});
16245+
1618216246
it("should return DeferredData on symbol with status + headers", async () => {
1618316247
let result = await queryRoute(
1618416248
createRequest("/parent/deferred?status")

packages/router/utils.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,7 +1317,7 @@ export class DeferredData {
13171317
// We store a little wrapper promise that will be extended with
13181318
// _data/_error props upon resolve/reject
13191319
let promise: TrackedPromise = Promise.race([value, this.abortPromise]).then(
1320-
(data) => this.onSettle(promise, key, null, data as unknown),
1320+
(data) => this.onSettle(promise, key, undefined, data as unknown),
13211321
(error) => this.onSettle(promise, key, error as unknown)
13221322
);
13231323

@@ -1351,7 +1351,19 @@ export class DeferredData {
13511351
this.unlistenAbortSignal();
13521352
}
13531353

1354-
if (error) {
1354+
// If the promise was resolved/rejected with undefined, we'll throw an error as you
1355+
// should always resolve with a value or null
1356+
if (error === undefined && data === undefined) {
1357+
let undefinedError = new Error(
1358+
`Deferred data for key "${key}" resolved/rejected with \`undefined\`, ` +
1359+
`you must resolve/reject with a value or \`null\`.`
1360+
);
1361+
Object.defineProperty(promise, "_error", { get: () => undefinedError });
1362+
this.emit(false, key);
1363+
return Promise.reject(undefinedError);
1364+
}
1365+
1366+
if (data === undefined) {
13551367
Object.defineProperty(promise, "_error", { get: () => error });
13561368
this.emit(false, key);
13571369
return Promise.reject(error);

0 commit comments

Comments
 (0)