From bf4667b03162d8d488586b1f1c1f78d8ba87761d Mon Sep 17 00:00:00 2001 From: PJ Date: Sun, 26 May 2024 20:07:49 -0400 Subject: [PATCH] next() and until() should not resume inside rule --- specs/jobs.spec.ts | 20 ++++++++++---------- specs/signals.spec.ts | 12 ++++++------ src/signals.ts | 9 +++++---- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/specs/jobs.spec.ts b/specs/jobs.spec.ts index b45121e..24c1a8c 100644 --- a/specs/jobs.spec.ts +++ b/specs/jobs.spec.ts @@ -676,7 +676,7 @@ describe("Async Ops", () => { // Then it should remain suspended see(); // Until the value *changes*, even if false - v.set(0); runRules(); see("0") + v.set(0); runRules(); clock.tick(0); see("0") }); it("asynchronously resuming when signal changes", () => { @@ -685,23 +685,23 @@ describe("Async Ops", () => { suspendOn(next(v)); clock.runAll(); runRules(); see(); // When the changes and rules run v.set(55); see(); runRules(); - // Then the next should resume with the new value - see("55"); + // Then the next should resume asynchronously with the new value + clock.tick(0); see("55"); }); it("throwing when a signal throws synchronously", () => { // When a suspended next() is run on an immediately throwing signal suspendOn(next(cached(() => {throw "boom"}))); clock.runAll(); runRules() - // Then it should immediately throw - see("err: boom"); + // Then it should asynchronously throw + see(); clock.tick(0); see("err: boom"); }); it("asynchronously throwing when a signal throws later", () => { // Given an async-throwing signal and an next() suspended on it const v = value(20), c = cached(() => { if (v()) throw "boom!";}); suspendOn(next(c)); clock.runAll(); see(); // When the signal recomputes as an error - v.set(55); see(); runRules(); - // Then the next should reject with the error - see("err: boom!"); + v.set(55); see(); runRules(); see(); + // Then the next should asynchronously reject with the error + clock.tick(0); see("err: boom!"); }); }); it("throws on non-Waitables", () => { @@ -756,7 +756,7 @@ describe("Async Ops", () => { const v = value(0); suspendOn(until(v)); clock.runAll(); see(); // When the value becomes true and rules run - v.set(55); see(); runRules(); + v.set(55); see(); runRules(); clock.tick(0); // Then the until should resume with the new value see("55"); }); @@ -771,7 +771,7 @@ describe("Async Ops", () => { const v = value(0), c = cached(() => { if (v()) throw "boom!";}); suspendOn(until(c)); clock.runAll(); see(); // When the signal recomputes as an error - v.set(55); see(); runRules(); + v.set(55); see(); runRules(); clock.tick(0) // Then the until should reject with the error see("err: boom!"); }); diff --git a/specs/signals.spec.ts b/specs/signals.spec.ts index 7535aab..75aeb49 100644 --- a/specs/signals.spec.ts +++ b/specs/signals.spec.ts @@ -1,7 +1,7 @@ import { log, see, describe, expect, it, useRoot, useClock, clock } from "./dev_deps.ts"; import { runRules, value, cached, rule, peek, WriteConflict, Signal, Writable, must, recalcWhen, - DisposeFn, RecalcSource, mockSource, lazy, detached, each, sleep, isCancel, getJob, + DisposeFn, RecalcSource, mockSource, lazy, detached, each, sleep, SignalImpl, ConfigurableImpl, action } from "../mod.ts"; import { current } from "../src/ambient.ts"; @@ -110,16 +110,16 @@ describe("Signal Constructors/Interfaces", () => { // Then the subscriber should be run in the null context see("true"); c.end(); }); - it("cleans up its until() rules", () => { + it("doesn't resume until() inside a rule", () => { // Given a falsy value const v = value(false); // When it's waited for via until and then goes truthy for(const cb of v["uneventful.until"]()) { - cb(() => { log(isCancel(getJob().result())); }); + cb(() => { log(!!current.cell); }); } - v.set(true); runRules(); - // Then the resolve should be in a canceled job (rule) - see("true"); + v.set(true); runRules(); see(); + // Then the resolve should occur asynchronously without being in a rule + clock.tick(0); see("false"); }); }); describe(".setf()", () => { diff --git a/src/signals.ts b/src/signals.ts index 76a217a..cc197cf 100644 --- a/src/signals.ts +++ b/src/signals.ts @@ -6,6 +6,7 @@ import { reject, resolve } from "./results.ts"; import { UntilMethod } from "./sinks.ts"; import { SignalSource, Source } from "./streams.ts"; import { CallableObject } from "./utils.ts"; +import { defer } from "./defer.ts"; export { rule, runRules, type GenericMethodDecorator, type RuleFactory } from "./rules.ts" export { WriteConflict, CircularDependency } from "./cells.ts"; @@ -82,8 +83,8 @@ export class SignalImpl extends CallableObject> implements Si return yield (r => { let seen = false, res: T; rule(stop => { - try { res = this(); } catch(e) { stop(); reject(r, e); } - if (seen) { stop(); resolve(r, res); } + try { res = this(); } catch(e) { stop(); defer(reject.bind(null, r,e)); } + if (seen) { stop(); defer(resolve.bind(null, r, res)); } seen = true; }) }); @@ -95,8 +96,8 @@ export class SignalImpl extends CallableObject> implements Si try { res = this(); } catch(e) { reject(r, e); return; } if (res) return resolve(r, res); rule(stop => { - try { res = this(); } catch(e) { stop(); reject(r,e); } - if (res) { stop(); resolve(r, res); } + try { res = this(); } catch(e) { stop(); defer(reject.bind(null, r,e)); } + if (res) { stop(); defer(resolve.bind(null, r, res)); } }); }); }