Skip to content

Conversation

Rich-Harris
Copy link
Member

@Rich-Harris Rich-Harris commented Sep 29, 2025

This is part of a project to update some of the async batching logic to handle a few edge cases that have cropped up.

The observable change here is that if you have an await expression that runs twice, and the second one somehow resolves before the first, they are no longer forced to resolve in linear order. The easiest way to see it is in this example — click a++ then b++ in quick succession:

  • main — the fast b++ update is blocked on the slow a++ update
  • this PR — the b++ update occurs, followed by the a++ update

To make this work, whenever a batch is committed, we essentially 'rebase' other pending batches on top of the newly applied state. In the example above, when we commit the fast b++ update, we re-run any async effects that depend on b in the context of the pending a++ batch.

Before submitting the PR, please make sure you do the following

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • Prefix your PR title with feat:, fix:, chore:, or docs:.
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.
  • If this PR changes code within packages/svelte/src, add a changeset (npx changeset).

Tests and linting

  • Run the tests with pnpm test and lint the project with pnpm lint

Copy link

changeset-bot bot commented Sep 29, 2025

🦋 Changeset detected

Latest commit: a50d0ec

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
svelte Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@svelte-docs-bot
Copy link

Copy link
Contributor

Playground

pnpm add https://pkg.pr.new/svelte@16866

@Rich-Harris
Copy link
Member Author

moving back to draft, some more improvements incoming

@Rich-Harris Rich-Harris marked this pull request as draft September 29, 2025 20:45
@Rich-Harris Rich-Harris marked this pull request as ready for review September 29, 2025 20:50
@Rich-Harris
Copy link
Member Author

In addition to the tests that are updated in the PR, this fixes the async-redirect test which, for reasons I don't begin to understand, passes on main despite being completely broken. Press 'b', then 'ok', then 'b' again and observe the difference in behaviour:

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks awesome. I'll wait for Simon to review as well before approving; I think everything is good but given he's more familiar with the clientside stuff he should definitely take a look

Comment on lines -18 to -27
// TODO why is this necessary? why isn't `await tick()` enough?
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
flushSync();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😆 I saw this while I was working on async SSR and I was really hopeful we could murder it at some point

Comment on lines 329 to 333
let is_earlier = true;

for (const batch of batches) {
if (batch === this) {
is_earlier = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this concerns me - what was this logic supposed to do? Right now it's doing nothing because it's true before and after, and when I change this to is_earlier = false tests start failing

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, yeah i borked this up didn't i. updated to the following — basically, if I have three updates and all of them involve foo, then if the second of the three resolves first I want to redo the first batch with the updated foo value, but let the third batch keep its current, more recent value

for (const [source, value] of this.current) {
	if (batch.current.has(source)) {
		if (is_earlier) {
			// bring the value up to date
			batch.current.set(source, value);
		} else {
			// later batch has more recent value,
			// no need to re-run these effects
			continue;
		}
	}

	mark_effects(source);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants