Skip to content

feat: Respect abort signals during serverside fetch optimization #13877

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 41 additions & 4 deletions packages/kit/src/runtime/server/fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,7 @@ export function create_fetch({ event, options, manifest, state, get_cookie_heade
);
}

const response = await respond(request, options, manifest, {
...state,
depth: state.depth + 1
});
const response = await internal_fetch(request, options, manifest, state);

const set_cookie = response.headers.get('set-cookie');
if (set_cookie) {
Expand Down Expand Up @@ -195,3 +192,43 @@ function normalize_fetch_input(info, init, url) {

return new Request(typeof info === 'string' ? new URL(info, url) : info, init);
}

/**
* @param {Request} request
* @param {import('types').SSROptions} options
* @param {import('@sveltejs/kit').SSRManifest} manifest
* @param {import('types').SSRState} state
* @returns {Promise<Response>}
*/
async function internal_fetch(request, options, manifest, state) {
if (request.signal) {
if (request.signal.aborted) {
throw new DOMException('The operation was aborted.', 'AbortError');
}

let remove_abort_listener = () => {};
/** @type {Promise<never>} */
const abort_promise = new Promise((_, reject) => {
const on_abort = () => {
reject(new DOMException('The operation was aborted.', 'AbortError'));
};
request.signal.addEventListener('abort', on_abort, { once: true });
remove_abort_listener = () => request.signal.removeEventListener('abort', on_abort);
});

const result = await Promise.race([
respond(request, options, manifest, {
...state,
depth: state.depth + 1
}),
abort_promise
]);
remove_abort_listener();
return result;
} else {
return await respond(request, options, manifest, {
...state,
depth: state.depth + 1
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export async function load({ fetch }) {
const aborted_controller = new AbortController();
aborted_controller.abort();

let aborted_immediately = false;
try {
await fetch('/load/fetch-abort-signal/data', { signal: aborted_controller.signal });
} catch (error) {
if (error.name === 'AbortError') {
aborted_immediately = true;
}
}

let aborted_during_request = false;
try {
await fetch('/load/fetch-abort-signal/slow', { signal: AbortSignal.timeout(100) });
} catch (error) {
if (error.name === 'AbortError') {
aborted_during_request = true;
}
}

const successful_response = await fetch('/load/fetch-abort-signal/data');
const successful_data = await successful_response.json();

return {
aborted_immediately,
aborted_during_request,
successful_data
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>
export let data;
</script>

<div>
<h1>AbortSignal Test Results</h1>
<p class="aborted-immediately">Aborted immediately: {data.aborted_immediately}</p>
<p class="aborted-during-request">Aborted during request: {data.aborted_during_request}</p>
<p class="successful-data">Successful data: {JSON.stringify(data.successful_data)}</p>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { json } from '@sveltejs/kit';

export async function GET() {
return json({ message: 'success', timestamp: Date.now() });
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function GET() {
return new Promise(() => {});
}
8 changes: 8 additions & 0 deletions packages/kit/test/apps/basics/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,14 @@

expect(await page.textContent('h1')).toBe('404');
});

test('AbortSignal works with internal fetch optimization', async ({ page }) => {

Check warning on line 573 in packages/kit/test/apps/basics/test/test.js

View workflow job for this annotation

GitHub Actions / test-kit (20, ubuntu-latest, chromium)

flaky test: AbortSignal works with internal fetch optimization

retries: 2

Check warning on line 573 in packages/kit/test/apps/basics/test/test.js

View workflow job for this annotation

GitHub Actions / test-kit (22, ubuntu-latest, chromium)

flaky test: AbortSignal works with internal fetch optimization

retries: 2
await page.goto('/load/fetch-abort-signal');

expect(await page.textContent('.aborted-immediately')).toBe('Aborted immediately: true');
expect(await page.textContent('.aborted-during-request')).toBe('Aborted during request: true');
expect(await page.textContent('.successful-data')).toContain('"message":"success"');
});
});

test.describe('Nested layouts', () => {
Expand Down
Loading