|
1 | 1 | /** @import { Effect, Value } from '#client' */ |
2 | 2 |
|
3 | 3 | import { DESTROYED } from '#client/constants'; |
4 | | -import { is_runes } from '../context.js'; |
5 | | -import { capture, get_pending_boundary } from '../dom/blocks/boundary.js'; |
| 4 | +import { DEV } from 'esm-env'; |
| 5 | +import { component_context, is_runes, set_component_context } from '../context.js'; |
| 6 | +import { get_pending_boundary } from '../dom/blocks/boundary.js'; |
6 | 7 | import { invoke_error_boundary } from '../error-handling.js'; |
7 | | -import { active_effect } from '../runtime.js'; |
| 8 | +import { |
| 9 | + active_effect, |
| 10 | + active_reaction, |
| 11 | + set_active_effect, |
| 12 | + set_active_reaction |
| 13 | +} from '../runtime.js'; |
8 | 14 | import { current_batch } from './batch.js'; |
9 | | -import { async_derived, derived, derived_safe_equal } from './deriveds.js'; |
| 15 | +import { |
| 16 | + async_derived, |
| 17 | + current_async_effect, |
| 18 | + derived, |
| 19 | + derived_safe_equal, |
| 20 | + set_from_async_derived |
| 21 | +} from './deriveds.js'; |
| 22 | +import { queue_micro_task } from '../dom/task.js'; |
10 | 23 |
|
11 | 24 | /** |
12 | 25 | * |
@@ -49,3 +62,66 @@ export function flatten(sync, async, fn) { |
49 | 62 | boundary.error(error); |
50 | 63 | }); |
51 | 64 | } |
| 65 | + |
| 66 | +/** |
| 67 | + * Captures the current effect context so that we can restore it after |
| 68 | + * some asynchronous work has happened if `track` is true (so that e.g. |
| 69 | + * `await a + b` causes `b` to be registered as a dependency). |
| 70 | + * |
| 71 | + * If `track` is false, we just take a note of which async derived |
| 72 | + * brought us here, so that we can emit a `async_reactivity_loss` |
| 73 | + * warning when it's appropriate to do so. |
| 74 | + * |
| 75 | + * @param {boolean} track |
| 76 | + */ |
| 77 | +export function capture(track = true) { |
| 78 | + var previous_effect = active_effect; |
| 79 | + var previous_reaction = active_reaction; |
| 80 | + var previous_component_context = component_context; |
| 81 | + |
| 82 | + if (DEV && !track) { |
| 83 | + var previous_async_effect = current_async_effect; |
| 84 | + } |
| 85 | + |
| 86 | + return function restore() { |
| 87 | + if (track) { |
| 88 | + set_active_effect(previous_effect); |
| 89 | + set_active_reaction(previous_reaction); |
| 90 | + set_component_context(previous_component_context); |
| 91 | + } |
| 92 | + |
| 93 | + if (DEV) { |
| 94 | + set_from_async_derived(track ? null : previous_async_effect); |
| 95 | + } |
| 96 | + |
| 97 | + // prevent the active effect from outstaying its welcome |
| 98 | + // TODO this feels brittle |
| 99 | + queue_micro_task(exit); |
| 100 | + }; |
| 101 | +} |
| 102 | + |
| 103 | +/** |
| 104 | + * Wraps an `await` expression in such a way that the effect context that was |
| 105 | + * active before the expression evaluated can be reapplied afterwards — |
| 106 | + * `await a + b` becomes `(await $.save(a))() + b` |
| 107 | + * @template T |
| 108 | + * @param {Promise<T>} promise |
| 109 | + * @param {boolean} [track] |
| 110 | + * @returns {Promise<() => T>} |
| 111 | + */ |
| 112 | +export async function save(promise, track = true) { |
| 113 | + var restore = capture(track); |
| 114 | + var value = await promise; |
| 115 | + |
| 116 | + return () => { |
| 117 | + restore(); |
| 118 | + return value; |
| 119 | + }; |
| 120 | +} |
| 121 | + |
| 122 | +function exit() { |
| 123 | + set_active_effect(null); |
| 124 | + set_active_reaction(null); |
| 125 | + set_component_context(null); |
| 126 | + if (DEV) set_from_async_derived(null); |
| 127 | +} |
0 commit comments