warn on reactivity loss

aa-coordination
Rich Harris 8 months ago
parent b4ee140632
commit 148ffd2783

@ -34,6 +34,14 @@ function add() {
} }
``` ```
### await_reactivity_loss
```
Detected reactivity loss
```
TODO
### await_waterfall ### await_waterfall
``` ```

@ -30,6 +30,12 @@ function add() {
} }
``` ```
## await_reactivity_loss
> Detected reactivity loss
TODO
## await_waterfall ## await_waterfall
> Detected an unnecessary async waterfall > Detected an unnecessary async waterfall

@ -1,5 +1,6 @@
/** @import { AwaitExpression, Expression } from 'estree' */ /** @import { AwaitExpression, Expression } from 'estree' */
/** @import { Context } from '../types' */ /** @import { Context } from '../types' */
import { dev } from '../../../../state.js';
import * as b from '../../../../utils/builders.js'; import * as b from '../../../../utils/builders.js';
/** /**
@ -7,15 +8,19 @@ import * as b from '../../../../utils/builders.js';
* @param {Context} context * @param {Context} context
*/ */
export function AwaitExpression(node, context) { export function AwaitExpression(node, context) {
const suspend = context.state.analysis.context_preserving_awaits.has(node); const save = context.state.analysis.context_preserving_awaits.has(node);
if (!suspend) {
return context.next();
}
if (dev || save) {
return b.call( return b.call(
b.await( b.await(
b.call('$.save', node.argument && /** @type {Expression} */ (context.visit(node.argument))) b.call(
'$.save',
node.argument && /** @type {Expression} */ (context.visit(node.argument)),
!save && b.false
)
) )
); );
}
return context.next();
} }

@ -30,6 +30,8 @@ import {
import { get_next_sibling } from '../operations.js'; import { get_next_sibling } from '../operations.js';
import { queue_boundary_micro_task } from '../task.js'; import { queue_boundary_micro_task } from '../task.js';
import * as e from '../../../shared/errors.js'; import * as e from '../../../shared/errors.js';
import { DEV } from 'esm-env';
import { from_async_derived, set_from_async_derived } from '../../reactivity/deriveds.js';
const ASYNC_INCREMENT = Symbol(); const ASYNC_INCREMENT = Symbol();
const ASYNC_DECREMENT = Symbol(); const ASYNC_DECREMENT = Symbol();
@ -340,15 +342,23 @@ function move_effect(effect, fragment) {
} }
} }
export function capture() { export function capture(track = true) {
var previous_effect = active_effect; var previous_effect = active_effect;
var previous_reaction = active_reaction; var previous_reaction = active_reaction;
var previous_component_context = component_context; var previous_component_context = component_context;
if (DEV && !track) {
var was_from_async_derived = from_async_derived;
}
return function restore() { return function restore() {
if (track) {
set_active_effect(previous_effect); set_active_effect(previous_effect);
set_active_reaction(previous_reaction); set_active_reaction(previous_reaction);
set_component_context(previous_component_context); set_component_context(previous_component_context);
} else if (DEV) {
set_from_async_derived(was_from_async_derived);
}
// prevent the active effect from outstaying its welcome // prevent the active effect from outstaying its welcome
queue_boundary_micro_task(exit); queue_boundary_micro_task(exit);
@ -390,10 +400,11 @@ export function suspend() {
/** /**
* @template T * @template T
* @param {Promise<T>} promise * @param {Promise<T>} promise
* @param {boolean} [track]
* @returns {Promise<() => T>} * @returns {Promise<() => T>}
*/ */
export async function save(promise) { export async function save(promise, track = true) {
var restore = capture(); var restore = capture(track);
var value = await promise; var value = await promise;
return () => { return () => {

@ -29,6 +29,14 @@ import { tracing_mode_flag } from '../../flags/index.js';
import { capture, suspend } from '../dom/blocks/boundary.js'; import { capture, suspend } from '../dom/blocks/boundary.js';
import { component_context } from '../context.js'; import { component_context } from '../context.js';
/** @type {Effect | null} */
export let from_async_derived = null;
/** @param {Effect | null} v */
export function set_from_async_derived(v) {
from_async_derived = v;
}
/** /**
* @template V * @template V
* @param {() => V} fn * @param {() => V} fn
@ -88,8 +96,11 @@ export function async_derived(fn) {
var promise = /** @type {Promise<V>} */ (/** @type {unknown} */ (undefined)); var promise = /** @type {Promise<V>} */ (/** @type {unknown} */ (undefined));
var value = source(/** @type {V} */ (undefined)); var value = source(/** @type {V} */ (undefined));
// TODO this isn't a block
block(async () => { block(async () => {
if (DEV) from_async_derived = active_effect;
var current = (promise = fn()); var current = (promise = fn());
if (DEV) from_async_derived = null;
var restore = capture(); var restore = capture();
var unsuspend = suspend(); var unsuspend = suspend();
@ -103,6 +114,8 @@ export function async_derived(fn) {
if (promise === current) { if (promise === current) {
restore(); restore();
from_async_derived = null;
internal_set(value, v); internal_set(value, v);
} }
} catch (e) { } catch (e) {

@ -37,6 +37,7 @@ import {
destroy_derived, destroy_derived,
destroy_derived_effects, destroy_derived_effects,
execute_derived, execute_derived,
from_async_derived,
update_derived update_derived
} from './reactivity/deriveds.js'; } from './reactivity/deriveds.js';
import * as e from './errors.js'; import * as e from './errors.js';
@ -51,6 +52,7 @@ import {
set_dev_current_component_function set_dev_current_component_function
} from './context.js'; } from './context.js';
import { add_boundary_effect, commit_boundary } from './dom/blocks/boundary.js'; import { add_boundary_effect, commit_boundary } from './dom/blocks/boundary.js';
import * as w from './warnings.js';
const FLUSH_MICROTASK = 0; const FLUSH_MICROTASK = 0;
const FLUSH_SYNC = 1; const FLUSH_SYNC = 1;
@ -967,6 +969,15 @@ export function get(signal) {
captured_signals.add(signal); captured_signals.add(signal);
} }
if (DEV && from_async_derived) {
var tracking = (from_async_derived.f & REACTION_IS_UPDATING) !== 0;
var was_read = from_async_derived.deps !== null && from_async_derived.deps.includes(signal);
if (!tracking && !was_read) {
w.await_reactivity_loss();
}
}
// Register the dependency on the current reaction signal. // Register the dependency on the current reaction signal.
if (active_reaction !== null && !untracking) { if (active_reaction !== null && !untracking) {
if (derived_sources !== null && derived_sources.includes(signal)) { if (derived_sources !== null && derived_sources.includes(signal)) {

@ -18,6 +18,17 @@ export function assignment_value_stale(property, location) {
} }
} }
/**
* Detected reactivity loss
*/
export function await_reactivity_loss() {
if (DEV) {
console.warn(`%c[svelte] await_reactivity_loss\n%cDetected reactivity loss\nhttps://svelte.dev/e/await_reactivity_loss`, bold, normal);
} else {
console.warn(`https://svelte.dev/e/await_reactivity_loss`);
}
}
/** /**
* Detected an unnecessary async waterfall * Detected an unnecessary async waterfall
*/ */

Loading…
Cancel
Save