Merge branch 'async' into aa-coordination-resource

aa-coordination-resource
Dominic Gannaway 7 months ago
commit b6c0268eb4

@ -45,7 +45,7 @@ TODO
### await_waterfall
```
Detected an unnecessary async waterfall
An async value (%location%) was not read immediately after it resolved. This often indicates an unnecessary waterfall, which can slow down your app.
```
TODO

@ -38,7 +38,7 @@ TODO
## await_waterfall
> Detected an unnecessary async waterfall
> An async value (%location%) was not read immediately after it resolved. This often indicates an unnecessary waterfall, which can slow down your app.
TODO

@ -1,7 +1,7 @@
/** @import { CallExpression, Expression, Identifier, Literal, VariableDeclaration, VariableDeclarator } from 'estree' */
/** @import { Binding } from '#compiler' */
/** @import { ComponentClientTransformState, ComponentContext } from '../types' */
import { dev } from '../../../../state.js';
import { dev, is_ignored, locate_node } from '../../../../state.js';
import { extract_paths } from '../../../../utils/ast.js';
import * as b from '../../../../utils/builders.js';
import * as assert from '../../../../utils/assert.js';
@ -19,7 +19,7 @@ export function VariableDeclaration(node, context) {
if (context.state.analysis.runes) {
for (const declarator of node.declarations) {
const init = declarator.init;
const init = /** @type {Expression} */ (declarator.init);
const rune = get_rune(init, context.state.scope);
if (
@ -164,6 +164,8 @@ export function VariableDeclaration(node, context) {
if (declarator.id.type === 'Identifier') {
if (is_async) {
const location = dev && is_ignored(init, 'await_waterfall') && locate_node(init);
declarations.push(
b.declarator(
declarator.id,
@ -173,7 +175,8 @@ export function VariableDeclaration(node, context) {
'$.save',
b.call(
'$.async_derived',
rune === '$derived.by' ? value : b.thunk(value, true)
b.thunk(value, true),
location ? b.literal(location) : undefined
)
)
)

@ -39,6 +39,7 @@ export const NAMESPACE_MATHML = 'http://www.w3.org/1998/Math/MathML';
// we use a list of ignorable runtime warnings because not every runtime warning
// can be ignored and we want to keep the validation for svelte-ignore in place
export const IGNORABLE_RUNTIME_WARNINGS = /** @type {const} */ ([
'await_waterfall',
'state_snapshot_uncloneable',
'binding_property_non_reactive',
'hydration_attribute_changed',

@ -14,7 +14,7 @@ export function async(node, expressions, fn) {
var restore = capture();
var unsuspend = suspend();
Promise.all(expressions.map(async_derived)).then((result) => {
Promise.all(expressions.map((fn) => async_derived(fn))).then((result) => {
restore();
fn(node, ...result);
unsuspend();

@ -136,6 +136,12 @@ export function boundary(node, props, children) {
}
function reset() {
async_count = 0;
if ((boundary.f & BOUNDARY_SUSPENDED) !== 0) {
boundary.f ^= BOUNDARY_SUSPENDED;
}
if (failed_effect !== null) {
pause_effect(failed_effect, () => {
failed_effect = null;
@ -151,6 +157,11 @@ export function boundary(node, props, children) {
reset_is_throwing_error();
}
});
if (async_count > 0) {
boundary.f |= BOUNDARY_SUSPENDED;
show_pending_snippet(true);
}
}
function unsuspend() {
@ -367,12 +378,7 @@ export function boundary(node, props, children) {
});
});
} else {
main_effect = branch(() => children(anchor));
if (async_count > 0) {
boundary.f |= BOUNDARY_SUSPENDED;
show_pending_snippet(true);
}
reset();
}
reset_is_throwing_error();

@ -86,11 +86,11 @@ export function derived(fn) {
/**
* @template V
* @param {() => Promise<V>} fn
* @param {boolean} detect_waterfall Whether to print a warning if the value is not read immediately after update
* @param {string} [location] If provided, print a warning if the value is not read immediately after update
* @returns {Promise<Source<V>>}
*/
/*#__NO_SIDE_EFFECTS__*/
export function async_derived(fn, detect_waterfall = true) {
export function async_derived(fn, location) {
let parent = /** @type {Effect | null} */ (active_effect);
if (parent === null) {
@ -127,12 +127,12 @@ export function async_derived(fn, detect_waterfall = true) {
internal_set(signal, v);
if (DEV && detect_waterfall) {
if (DEV && location !== undefined) {
recent_async_deriveds.add(signal);
setTimeout(() => {
if (recent_async_deriveds.has(signal)) {
w.await_waterfall();
w.await_waterfall(location);
recent_async_deriveds.delete(signal);
}
});
@ -145,7 +145,9 @@ export function async_derived(fn, detect_waterfall = true) {
}
},
(e) => {
handle_error(e, parent, null, parent.ctx);
if (promise === current) {
handle_error(e, parent, null, parent.ctx);
}
}
);
}, EFFECT_PRESERVED);

@ -352,7 +352,7 @@ export function template_effect(fn, sync = [], async = [], d = derived) {
var restore = capture();
var unsuspend = suspend();
Promise.all(async.map((expression) => async_derived(expression, false))).then((result) => {
Promise.all(async.map((expression) => async_derived(expression))).then((result) => {
restore();
if ((effect.f & DESTROYED) !== 0) {

@ -30,11 +30,12 @@ export function await_reactivity_loss() {
}
/**
* Detected an unnecessary async waterfall
* An async value (%location%) was not read immediately after it resolved. This often indicates an unnecessary waterfall, which can slow down your app.
* @param {string} location
*/
export function await_waterfall() {
export function await_waterfall(location) {
if (DEV) {
console.warn(`%c[svelte] await_waterfall\n%cDetected an unnecessary async waterfall\nhttps://svelte.dev/e/await_waterfall`, bold, normal);
console.warn(`%c[svelte] await_waterfall\n%cAn async value (${location}) was not read immediately after it resolved. This often indicates an unnecessary waterfall, which can slow down your app.\nhttps://svelte.dev/e/await_waterfall`, bold, normal);
} else {
console.warn(`https://svelte.dev/e/await_waterfall`);
}

@ -0,0 +1,37 @@
import { flushSync, tick } from 'svelte';
import { deferred } from '../../../../src/internal/shared/utils.js';
import { test } from '../../test';
/** @type {ReturnType<typeof deferred>} */
let d;
export default test({
html: `<p>pending</p>`,
get props() {
d = deferred();
return {
promise: d.promise
};
},
async test({ assert, target, component }) {
d.reject(new Error('oops!'));
await Promise.resolve();
await Promise.resolve();
flushSync();
assert.htmlEqual(target.innerHTML, '<p>oops!</p><button>reset</button>');
const button = target.querySelector('button');
component.promise = (d = deferred()).promise;
flushSync(() => button?.click());
assert.htmlEqual(target.innerHTML, '<p>pending</p>');
d.resolve('wheee');
await Promise.resolve();
await tick();
assert.htmlEqual(target.innerHTML, '<h1>wheee</h1>');
}
});

@ -0,0 +1,16 @@
<script>
let { promise } = $props();
</script>
<svelte:boundary>
<h1>{await promise}</h1>
{#snippet pending()}
<p>pending</p>
{/snippet}
{#snippet failed(error, reset)}
<p>{error.message}</p>
<button onclick={reset}>reset</button>
{/snippet}
</svelte:boundary>
Loading…
Cancel
Save