Merge branch 'async-local-effect-pending' into async

pull/15844/head
Rich Harris 3 months ago
commit 8a1ab922ce

@ -64,7 +64,7 @@ export function CallExpression(node, context) {
); );
case '$effect.pending': case '$effect.pending':
return b.call('$.get', b.id('$.pending')); return b.call(b.id('$.pending'));
case '$inspect': case '$inspect':
case '$inspect().with': case '$inspect().with':

@ -17,7 +17,7 @@ export async function async(node, expressions, fn) {
var restore = capture(); var restore = capture();
var boundary = get_pending_boundary(); var boundary = get_pending_boundary();
boundary.increment(); boundary.update_pending_count(1);
try { try {
const result = await Promise.all(expressions.map((fn) => async_derived(fn))); const result = await Promise.all(expressions.map((fn) => async_derived(fn)));
@ -29,6 +29,6 @@ export async function async(node, expressions, fn) {
} catch (error) { } catch (error) {
boundary.error(error); boundary.error(error);
} finally { } finally {
boundary.decrement(); boundary.update_pending_count(-1);
} }
} }

@ -7,6 +7,7 @@ import { block, branch, destroy_effect, pause_effect } from '../../reactivity/ef
import { import {
active_effect, active_effect,
active_reaction, active_reaction,
get,
set_active_effect, set_active_effect,
set_active_reaction set_active_reaction
} from '../../runtime.js'; } from '../../runtime.js';
@ -24,6 +25,8 @@ import * as e from '../../../shared/errors.js';
import { DEV } from 'esm-env'; import { DEV } from 'esm-env';
import { from_async_derived, set_from_async_derived } from '../../reactivity/deriveds.js'; import { from_async_derived, set_from_async_derived } from '../../reactivity/deriveds.js';
import { Batch } from '../../reactivity/batch.js'; import { Batch } from '../../reactivity/batch.js';
import { source, update } from '../../reactivity/sources.js';
import { tag } from '../../dev/tracing.js';
/** /**
* @typedef {{ * @typedef {{
@ -82,6 +85,8 @@ export class Boundary {
#pending_count = 0; #pending_count = 0;
#is_creating_fallback = false; #is_creating_fallback = false;
effect_pending = source(0);
/** /**
* @param {TemplateNode} node * @param {TemplateNode} node
* @param {BoundaryProps} props * @param {BoundaryProps} props
@ -98,6 +103,10 @@ export class Boundary {
this.pending = !!this.#props.pending; this.pending = !!this.#props.pending;
if (DEV) {
tag(this.effect_pending, '$effect.pending()');
}
this.#effect = block(() => { this.#effect = block(() => {
/** @type {Effect} */ (active_effect).b = this; /** @type {Effect} */ (active_effect).b = this;
@ -210,19 +219,26 @@ export class Boundary {
} }
} }
increment() { /** @param {1 | -1} d */
this.#pending_count++; #update_pending_count(d) {
} this.#pending_count += d;
decrement() { if (this.#pending_count === 0) {
if (--this.#pending_count === 0) {
this.commit(); this.commit();
if (this.#main_effect !== null) {
// TODO do we also need to `resume_effect` here?
// schedule_effect(this.#main_effect);
} }
} }
/** @param {1 | -1} d */
update_pending_count(d) {
if (this.has_pending_snippet()) {
this.#update_pending_count(d);
} else if (this.parent) {
this.parent.#update_pending_count(d);
}
queueMicrotask(() => {
update(this.effect_pending, d);
});
} }
/** @param {unknown} error */ /** @param {unknown} error */
@ -373,10 +389,10 @@ export function capture(track = true) {
export function suspend() { export function suspend() {
let boundary = get_pending_boundary(); let boundary = get_pending_boundary();
boundary.increment(); boundary.update_pending_count(1);
return function unsuspend() { return function unsuspend() {
boundary.decrement(); boundary.update_pending_count(-1);
}; };
} }
@ -401,3 +417,14 @@ function exit() {
set_active_reaction(null); set_active_reaction(null);
set_component_context(null); set_component_context(null);
} }
export function pending() {
// TODO throw helpful error if called outside an effect
const boundary = /** @type {Effect} */ (active_effect).b;
if (boundary === null) {
return 0; // TODO eventually we will need this to be global
}
return get(boundary.effect_pending);
}

@ -115,15 +115,7 @@ export {
user_effect, user_effect,
user_pre_effect user_pre_effect
} from './reactivity/effects.js'; } from './reactivity/effects.js';
export { export { mutable_source, mutate, set, state, update, update_pre } from './reactivity/sources.js';
mutable_source,
mutate,
pending,
set,
state,
update,
update_pre
} from './reactivity/sources.js';
export { export {
prop, prop,
rest_props, rest_props,
@ -143,7 +135,7 @@ export {
update_store, update_store,
mark_store_binding mark_store_binding
} from './reactivity/store.js'; } from './reactivity/store.js';
export { boundary, save, suspend } from './dom/blocks/boundary.js'; export { boundary, pending, save, suspend } from './dom/blocks/boundary.js';
export { set_text } from './render.js'; export { set_text } from './render.js';
export { export {
get, get,

@ -10,8 +10,6 @@ import {
set_signal_status, set_signal_status,
update_effect update_effect
} from '../runtime.js'; } from '../runtime.js';
import { raf } from '../timing.js';
import { internal_set, pending } from './sources.js';
/** @type {Set<Batch>} */ /** @type {Set<Batch>} */
const batches = new Set(); const batches = new Set();
@ -19,11 +17,6 @@ const batches = new Set();
/** @type {Batch | null} */ /** @type {Batch | null} */
export let current_batch = null; export let current_batch = null;
/** Update `$effect.pending()` */
function update_pending() {
internal_set(pending, batches.size > 0);
}
/** @type {Map<Derived, any> | null} */ /** @type {Map<Derived, any> | null} */
export let batch_deriveds = null; export let batch_deriveds = null;
@ -239,8 +232,6 @@ export class Batch {
} }
this.#callbacks.clear(); this.#callbacks.clear();
raf.tick(update_pending);
} }
increment() { increment() {
@ -295,10 +286,6 @@ export class Batch {
static ensure() { static ensure() {
if (current_batch === null) { if (current_batch === null) {
if (batches.size === 0) {
raf.tick(update_pending);
}
const batch = (current_batch = new Batch()); const batch = (current_batch = new Batch());
batches.add(current_batch); batches.add(current_batch);
@ -315,3 +302,13 @@ export class Batch {
return current_batch; return current_batch;
} }
} }
/**
* Forcibly remove all current batches
* TODO investigate why we need this in tests
*/
export function clear() {
for (const batch of batches) {
batch.remove();
}
}

@ -31,7 +31,7 @@ import { destroy_effect, render_effect } from './effects.js';
import { inspect_effects, internal_set, set_inspect_effects, source } from './sources.js'; import { inspect_effects, internal_set, set_inspect_effects, source } from './sources.js';
import { get_stack } from '../dev/tracing.js'; import { get_stack } from '../dev/tracing.js';
import { tracing_mode_flag } from '../../flags/index.js'; import { tracing_mode_flag } from '../../flags/index.js';
import { get_pending_boundary } from '../dom/blocks/boundary.js'; import { Boundary, get_pending_boundary } from '../dom/blocks/boundary.js';
import { component_context } from '../context.js'; import { component_context } from '../context.js';
import { UNINITIALIZED } from '../../../constants.js'; import { UNINITIALIZED } from '../../../constants.js';
import { current_batch } from './batch.js'; import { current_batch } from './batch.js';
@ -105,7 +105,7 @@ export function async_derived(fn, location) {
throw new Error('TODO cannot create unowned async derived'); throw new Error('TODO cannot create unowned async derived');
} }
let boundary = get_pending_boundary(); var boundary = /** @type {Boundary} */ (parent.b);
var promise = /** @type {Promise<V>} */ (/** @type {unknown} */ (undefined)); var promise = /** @type {Promise<V>} */ (/** @type {unknown} */ (undefined));
var signal = source(/** @type {V} */ (UNINITIALIZED)); var signal = source(/** @type {V} */ (UNINITIALIZED));
@ -135,7 +135,8 @@ export function async_derived(fn, location) {
var pending = boundary.pending; var pending = boundary.pending;
if (should_suspend) { if (should_suspend) {
(pending ? boundary : batch).increment(); boundary.update_pending_count(1);
if (!pending) batch.increment();
} }
/** /**
@ -148,7 +149,8 @@ export function async_derived(fn, location) {
from_async_derived = null; from_async_derived = null;
if (should_suspend) { if (should_suspend) {
(pending ? boundary : batch).decrement(); boundary.update_pending_count(-1);
if (!pending) batch.decrement();
} }
if (!pending) batch.restore(); if (!pending) batch.restore();

@ -43,9 +43,6 @@ export let inspect_effects = new Set();
/** @type {Map<Source, any>} */ /** @type {Map<Source, any>} */
export const old_values = new Map(); export const old_values = new Map();
/** Internal representation of `$effect.pending()` */
export let pending = source(false);
/** /**
* @param {Set<any>} v * @param {Set<any>} v
*/ */

@ -11,6 +11,7 @@ import { assert_html_equal, assert_html_equal_with_options } from '../html_equal
import { raf } from '../animation-helpers.js'; import { raf } from '../animation-helpers.js';
import type { CompileOptions } from '#compiler'; import type { CompileOptions } from '#compiler';
import { suite_with_variants, type BaseTest } from '../suite.js'; import { suite_with_variants, type BaseTest } from '../suite.js';
import { clear } from '../../src/internal/client/reactivity/batch.js';
type Assert = typeof import('vitest').assert & { type Assert = typeof import('vitest').assert & {
htmlEqual(a: string, b: string, description?: string): void; htmlEqual(a: string, b: string, description?: string): void;
@ -521,6 +522,8 @@ async function run_test_variant(
console.log = console_log; console.log = console_log;
console.warn = console_warn; console.warn = console_warn;
console.error = console_error; console.error = console_error;
clear();
} }
} }

Loading…
Cancel
Save