chore: emit `await_reactivity_loss` in `for await` loops

pull/16521/head
ComputerGuy 1 month ago
parent d82edf6b1d
commit 1c26d8f146

@ -0,0 +1,5 @@
---
'svelte': patch
---
chore: emit `await_reactivity_loss` in `for await` loops

@ -27,6 +27,7 @@ import { EachBlock } from './visitors/EachBlock.js';
import { ExportNamedDeclaration } from './visitors/ExportNamedDeclaration.js';
import { ExpressionStatement } from './visitors/ExpressionStatement.js';
import { Fragment } from './visitors/Fragment.js';
import { ForOfStatement } from './visitors/ForOfStatement.js';
import { FunctionDeclaration } from './visitors/FunctionDeclaration.js';
import { FunctionExpression } from './visitors/FunctionExpression.js';
import { HtmlTag } from './visitors/HtmlTag.js';
@ -104,6 +105,7 @@ const visitors = {
ExportNamedDeclaration,
ExpressionStatement,
Fragment,
ForOfStatement,
FunctionDeclaration,
FunctionExpression,
HtmlTag,

@ -0,0 +1,20 @@
/** @import { Expression, ForOfStatement, Pattern, Statement, VariableDeclaration } from 'estree' */
/** @import { ComponentContext } from '../types' */
import * as b from '#compiler/builders';
import { dev, is_ignored } from '../../../../state.js';
/**
* @param {ForOfStatement} node
* @param {ComponentContext} context
*/
export function ForOfStatement(node, context) {
if (node.await && dev && !is_ignored(node, 'await_reactivity_loss')) {
const left = /** @type {VariableDeclaration | Pattern} */ (context.visit(node.left));
const argument = /** @type {Expression} */ (context.visit(node.right));
const body = /** @type {Statement} */ (context.visit(node.body));
const right = b.call('$.for_await_track_reactivity_loss', argument);
return b.for_of(left, right, body, true);
}
context.next();
}

@ -214,6 +214,23 @@ export function export_default(declaration) {
return { type: 'ExportDefaultDeclaration', declaration };
}
/**
* @param {ESTree.VariableDeclaration | ESTree.Pattern} left
* @param {ESTree.Expression} right
* @param {ESTree.Statement} body
* @param {boolean} [await]
* @returns {ESTree.ForOfStatement}
*/
export function for_of(left, right, body, await = false) {
return {
type: 'ForOfStatement',
left,
right,
body,
await
};
}
/**
* @param {ESTree.Identifier} id
* @param {ESTree.Pattern[]} params

@ -98,7 +98,11 @@ export {
props_id,
with_script
} from './dom/template.js';
export { save, track_reactivity_loss } from './reactivity/async.js';
export {
for_await_track_reactivity_loss,
save,
track_reactivity_loss
} from './reactivity/async.js';
export { flushSync as flush, suspend } from './reactivity/batch.js';
export {
async_derived,

@ -119,6 +119,44 @@ export async function track_reactivity_loss(promise) {
};
}
/**
* Used in `for await` loops in DEV, so
* that we can emit `await_reactivity_loss` warnings
* after each `async_iterator` result resolves and
* after the `async_iterator` return resolves (if it runs)
* @template T
* @template TReturn
* @param {AsyncIterator<T, TReturn>} async_iterator
* @returns {AsyncGenerator<T, TReturn | undefined>}
*/
export async function* for_await_track_reactivity_loss(async_iterator) {
// This is based on the algorithms described in ECMA-262:
// ForIn/OfBodyEvaluation
// https://tc39.es/ecma262/multipage/ecmascript-language-statements-and-declarations.html#sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset
// AsyncIteratorClose
// https://tc39.es/ecma262/multipage/abstract-operations.html#sec-asynciteratorclose
/** Whether the completion of the iterator was "normal", meaning it wasn't ended via `break` or a similar method */
let normal_completion = false;
try {
while (true) {
const { done, value } = (await track_reactivity_loss(async_iterator.next()))();
if (done) {
normal_completion = true;
break;
}
yield value;
}
} finally {
// If the iterator had a normal completion and `return` is defined on the iterator, call it and return the value
if (normal_completion && async_iterator.return !== undefined) {
return /** @type {TReturn} */ (
(await track_reactivity_loss(async_iterator.return()))().value
);
}
}
}
export function unset_context() {
set_active_effect(null);
set_active_reaction(null);

Loading…
Cancel
Save