sveltejs / sveltejs/kit

Abortable PageLoad functions with AbortSignal

Open
#9,637 2 comments 19 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
20.8k
Forks
2.3k
Avg merge
1d 16h
Merged PRs (30d)
156

Description

Describe the problem

I have a SvelteKit application that has a "live"/"as you type" search driven by SvelteKit navigations with goto. For every character entered into the search form, a new navigation is kicked off to fetch the results for the search query. As a result, there can be a large number of concurrent loads ongoing, kicking off a large number of concurrent fetch requests, driven by this rapid series of navigations.

Only the results of the most recent load are useful to the application. Previous fetches continue to execute, requiring us to download their results, JSON.parse/devalue.parse them (quite expensive, in my case), massage the resulting data, etc, all of which wastes limited browser resources.

Describe the proposed solution

Ideally there would be a way SvelteKit could signal such loads that they are stale/obsolete due to newer navigations kicking off newer invocations of them. There is a standard API for this: AbortSignal. If LoadEvent were augmented with a signal: AbortSignal property, which SvelteKit would fire whenever the associated navigation became obsolete, user implementations of load could opt in to consuming that signal, passing it directly to fetch's RequestInit or to any other API that can be parameterized with it, or consuming it directly in any asynchronous operations.

I believe this would be backward compatible, as users could choose to simply ignore the given signal, and would be doing so by default.

Alternatives considered

Alternatively, PageServerLoad is currently provided with a full-blown request: Request property, which includes an AbortSignal. However, that signal is never invoked, nor do I think it could be without substantially complicating the network protocol: as far as I know, you can't receive direct notification that a client has aborted a request on the server; fetch aborts are done from the perspective of the client, the server just sees that the pipe is closed.

But, another option would be to provide a request: Request property on universal PageLoads, not just on PageServerLoad, and thus implicitly provide a signal along with it. That signal could start firing on clients. It would further unify the PageLoad and PageServerLoad (as opposed to just adding signal: AbortSignal to PageLoad, which would create a new divergence between them), at the expense of figuring out how to convincingly mock a Request that is in fact not ever going over the wire. Perhaps there's no particular magic to be done there, and just constructing a new Request() and handing it off is enough.

@khromov quite graciously created a demo (in response to me asking about this functionality on Discord) showing how to achieve such aborts today, by stashing AbortControllers for ongoing fetch requests and firing them outside of the router itself:
https://github.com/khromov/navigation-promise-cancel-demo/blob/master/src/routes/%2Bpage.svelte#L9-L16
https://github.com/khromov/navigation-promise-cancel-demo/blob/ba6055a935c69f7ddc304249e99404a8787248b0/src/routes/%2Bpage.ts#L12-L13
This is a way you can achieve a version of this feature today, but it feels pretty hacky 🥲.

Importance

would make my life easier

Additional Information

This proposal is made purely from a user's perspective; I have no idea how challenging it would be to actually evolve the API in the proposed ways. Nor do I know how SvelteKit handles rejected load functions for "stale" navigations today, the point being that this proposal would create many more of them, with AbortErrors. I tried to briefly scan the implementation of the client router and it's understandably too sophisticated to be so briefly analyzed 🌞.

By way of comparison, react-router, and thus Remix, already function as proposed. Its loader functions are given a full Request, which naturally includes an AbortSignal that fires when requests are aborted. I used this functionality before I rewrote the linked application in SvelteKit.

There is (at least!) one problem with applying any of the discussed options to SvelteKit: they do not cover aborting PageServerLoad requests in the browser. As far as I know, there is no surface in the browser to give users control over SvelteKit's requests to the server in this regard. I'm not sure what the least inappropriate way to do that would be. An arbitrary flag somewhere, like on goto? But I think expanding goto is an idea that's probably dead on arrival, given previous musings from Rich I've read on that topic. Perhaps a better idea would be to export a flag from the +page.server.js module, like export const abortable = true. Still kind of ugly.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the LoadEvent, PageLoad, PageServerLoad, and goto API entry points, then trace the client router's handling of stale navigations. Compare adding an AbortSignal with exposing a Request to universal loads, including browser-triggered PageServerLoad requests. Done means the abort behavior and API shape are defined for these cases.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
frontend, web-dev
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
28/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.