async deriveds

aaa
Rich Harris 8 months ago
parent 2cd6b73236
commit 32e12d03b3

@ -264,7 +264,8 @@ export function analyze_module(ast, options) {
accessors: false,
runes: true,
immutable: true,
tracing: analysis.tracing
tracing: analysis.tracing,
async_deriveds: new Set()
};
}
@ -451,7 +452,8 @@ export function analyze_component(root, source, options) {
undefined_exports: new Map(),
snippet_renderers: new Map(),
snippets: new Set(),
is_async: false
is_async: false,
async_deriveds: new Set()
};
if (!runes) {

@ -7,6 +7,7 @@ import { get_parent, unwrap_optional } from '../../../utils/ast.js';
import { is_pure, is_safe_identifier } from './shared/utils.js';
import { dev, locate_node, source } from '../../../state.js';
import * as b from '../../../utils/builders.js';
import { create_expression_metadata } from '../../nodes.js';
/**
* @param {CallExpression} node
@ -207,7 +208,19 @@ export function CallExpression(node, context) {
}
// `$inspect(foo)` or `$derived(foo) should not trigger the `static-state-reference` warning
if (rune === '$inspect' || rune === '$derived') {
if (rune === '$derived') {
const expression = create_expression_metadata();
context.next({
...context.state,
function_depth: context.state.function_depth + 1,
expression
});
if (expression.is_async) {
context.state.analysis.async_deriveds.add(node);
}
} else if (rune === '$inspect') {
context.next({ ...context.state, function_depth: context.state.function_depth + 1 });
} else {
context.next();

@ -158,13 +158,28 @@ export function VariableDeclaration(node, context) {
}
if (rune === '$derived' || rune === '$derived.by') {
const is_async = context.state.analysis.async_deriveds.has(
/** @type {CallExpression} */ (init)
);
if (declarator.id.type === 'Identifier') {
declarations.push(
b.declarator(
declarator.id,
b.call('$.derived', rune === '$derived.by' ? value : b.thunk(value))
)
);
if (is_async) {
declarations.push(
b.declarator(
declarator.id,
b.await(
b.call('$.async_derived', rune === '$derived.by' ? value : b.thunk(value, true))
)
)
);
} else {
declarations.push(
b.declarator(
declarator.id,
b.call('$.derived', rune === '$derived.by' ? value : b.thunk(value))
)
);
}
} else {
const bindings = extract_paths(declarator.id);

@ -1,4 +1,4 @@
/** @import { Identifier } from 'estree' */
/** @import { CallExpression, Identifier } from 'estree' */
/** @import { ComponentContext, Context } from '../../types' */
import { is_state_source } from '../../utils.js';
import * as b from '../../../../../utils/builders.js';
@ -17,6 +17,18 @@ export function get_value(node) {
*/
export function add_state_transformers(context) {
for (const [name, binding] of context.state.scope.declarations) {
if (
binding.kind === 'derived' &&
context.state.analysis.async_deriveds.has(/** @type {CallExpression} */ (binding.initial))
) {
// async deriveds are a special case
context.state.transform[name] = {
read: b.call
};
continue;
}
if (
is_state_source(binding, context.state.analysis) ||
binding.kind === 'derived' ||

@ -1,5 +1,5 @@
import type { AST, Binding } from '#compiler';
import type { Identifier, LabeledStatement, Node, Program } from 'estree';
import type { CallExpression, Identifier, LabeledStatement, Node, Program } from 'estree';
import type { Scope, ScopeRoot } from './scope.js';
export interface Js {
@ -31,6 +31,9 @@ export interface Analysis {
// TODO figure out if we can move this to ComponentAnalysis
accessors: boolean;
/** A set of deriveds that contain `await` expressions */
async_deriveds: Set<CallExpression>;
}
export interface ComponentAnalysis extends Analysis {

@ -97,7 +97,7 @@ export {
template_with_script,
text
} from './dom/template.js';
export { derived, derived_safe_equal } from './reactivity/deriveds.js';
export { async_derived, derived, derived_safe_equal } from './reactivity/deriveds.js';
export {
effect_tracking,
effect_root,

@ -18,14 +18,16 @@ import {
update_reaction,
increment_write_version,
set_active_effect,
component_context
component_context,
get
} from '../runtime.js';
import { equals, safe_equals } from './equality.js';
import * as e from '../errors.js';
import { destroy_effect } from './effects.js';
import { inspect_effects, set_inspect_effects } from './sources.js';
import { destroy_effect, render_effect } from './effects.js';
import { inspect_effects, internal_set, set_inspect_effects, source } from './sources.js';
import { get_stack } from '../dev/tracing.js';
import { tracing_mode_flag } from '../../flags/index.js';
import { preserve_context } from '../dom/blocks/boundary.js';
/**
* @template V
@ -75,6 +77,36 @@ export function derived(fn) {
return signal;
}
/**
* @template V
* @param {() => Promise<V>} fn
* @returns {Promise<() => V>}
*/
/*#__NO_SIDE_EFFECTS__*/
export async function async_derived(fn) {
if (!active_effect) {
throw new Error('TODO cannot create unowned async derived');
}
let promise = /** @type {Promise<V>} */ (/** @type {unknown} */ (undefined));
let value = source(/** @type {V} */ (undefined));
render_effect(() => {
const current = (promise = fn());
promise.then((v) => {
if (promise === current) {
internal_set(value, v);
}
});
// TODO what happens when the promise rejects?
});
(await preserve_context(promise)).read();
return () => get(value);
}
/**
* @template V
* @param {() => V} fn

Loading…
Cancel
Save