Skip to content

Commit dedda71

Browse files
authored
feat: add signal property to request (#14715)
* add signal * test * changeset
1 parent 9c933a2 commit dedda71

File tree

5 files changed

+65
-0
lines changed

5 files changed

+65
-0
lines changed

.changeset/gentle-stars-repair.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@sveltejs/kit': minor
3+
'@sveltejs/adapter-node': minor
4+
---
5+
6+
feat: add [`signal`](https://developer.mozilla.org/en-US/docs/Web/API/Request/signal) property to request

packages/kit/src/exports/node/index.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,25 @@ export async function getRequest({ request, base, bodySizeLimit }) {
120120
delete headers[':scheme'];
121121
}
122122

123+
// TODO: Whenever Node >=22 is minimum supported version, we can use `request.readableAborted`
124+
// @see https://github.com/nodejs/node/blob/5cf3c3e24c7257a0c6192ed8ef71efec8ddac22b/lib/internal/streams/readable.js#L1443-L1453
125+
const controller = new AbortController();
126+
let errored = false;
127+
let end_emitted = false;
128+
request.once('error', () => (errored = true));
129+
request.once('end', () => (end_emitted = true));
130+
request.once('close', () => {
131+
if ((errored || request.destroyed) && !end_emitted) {
132+
controller.abort();
133+
}
134+
});
135+
123136
return new Request(base + request.url, {
124137
// @ts-expect-error
125138
duplex: 'half',
126139
method: request.method,
127140
headers: Object.entries(headers),
141+
signal: controller.signal,
128142
body:
129143
request.method === 'GET' || request.method === 'HEAD'
130144
? undefined
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<script>
2+
import { onMount } from 'svelte';
3+
4+
let result;
5+
6+
function test_abort() {
7+
const controller = new AbortController();
8+
fetch('/request-abort', { method: 'POST', signal: controller.signal }).then((r) => r.json());
9+
setTimeout(() => {
10+
controller.abort();
11+
fetch('/request-abort', { headers: { accept: 'application/json' } }).then(
12+
async (r) => (result = await r.json())
13+
);
14+
}, 100);
15+
}
16+
17+
onMount(test_abort);
18+
</script>
19+
20+
<pre>{JSON.stringify(result)}</pre>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { json } from '@sveltejs/kit';
2+
3+
let aborted = false;
4+
5+
/** @type {import('@sveltejs/kit').RequestHandler} */
6+
export function GET() {
7+
return json({ aborted });
8+
}
9+
10+
/** @type {import('@sveltejs/kit').RequestHandler} */
11+
export async function POST({ request }) {
12+
request.signal.addEventListener('abort', () => (aborted = true));
13+
await new Promise((r) => setTimeout(r, 1000));
14+
return json({ ok: true });
15+
}

packages/kit/test/apps/dev-only/test/test.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,13 @@ test.describe('Vite', () => {
172172
expect(manifest).toHaveProperty('optimized.e2e-test-dep-hooks');
173173
});
174174
});
175+
176+
test.describe('request abort', () => {
177+
test.skip(({ javaScriptEnabled }) => !process.env.DEV || !javaScriptEnabled);
178+
179+
test('request.signal fires abort event', async ({ page }) => {
180+
await page.goto('/request-abort');
181+
await page.waitForTimeout(200);
182+
expect(await page.innerText('pre')).toBe('{"aborted":true}');
183+
});
184+
});

0 commit comments

Comments
 (0)