signalium 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # signalium
2
2
 
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - e8aa91a: Fix async init values
8
+
9
+ ## 0.2.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 4696d06: Refactor await and invalidate to make them more composable
14
+
3
15
  ## 0.1.1
4
16
 
5
17
  ### Patch Changes
package/dist/signals.d.ts CHANGED
@@ -10,10 +10,7 @@ export interface Signal<T = unknown> {
10
10
  export interface WriteableSignal<T> extends Signal<T> {
11
11
  set(value: T): void;
12
12
  }
13
- export interface AsyncSignal<T> extends Signal<AsyncResult<T>> {
14
- invalidate(): void;
15
- await(): T;
16
- }
13
+ export type AsyncSignal<T> = Signal<AsyncResult<T>>;
17
14
  export type SignalCompute<T> = (prev: T | undefined) => T;
18
15
  export type SignalAsyncCompute<T> = (prev: T | undefined) => T | Promise<T>;
19
16
  export type SignalWatcherEffect = () => void;
@@ -61,8 +58,6 @@ export declare class ComputedSignal<T> {
61
58
  _ref: WeakRef<ComputedSignal<T>>;
62
59
  constructor(type: SignalType, compute: SignalCompute<T> | SignalAsyncCompute<T> | SignalSubscribe<T> | undefined, equals?: SignalEquals<T>, initValue?: T);
63
60
  get(): T | AsyncResult<T>;
64
- invalidate(): void;
65
- await(): T;
66
61
  _check(shouldWatch?: boolean): number;
67
62
  _run(wasConnected: boolean, isConnected: boolean, shouldConnect: boolean): void;
68
63
  _resetDirty(): void;
@@ -70,23 +65,29 @@ export declare class ComputedSignal<T> {
70
65
  _dirtyConsumers(): void;
71
66
  _disconnect(count?: number): void;
72
67
  }
73
- export interface AsyncPending {
68
+ export interface AsyncBaseResult<T> {
69
+ invalidate(): void;
70
+ await(): T;
71
+ }
72
+ export interface AsyncPending<T> extends AsyncBaseResult<T> {
74
73
  result: undefined;
75
74
  error: unknown;
76
75
  isPending: boolean;
77
76
  isReady: false;
78
77
  isError: boolean;
79
78
  isSuccess: boolean;
79
+ didResolve: boolean;
80
80
  }
81
- export interface AsyncReady<T> {
81
+ export interface AsyncReady<T> extends AsyncBaseResult<T> {
82
82
  result: T;
83
83
  error: unknown;
84
84
  isPending: boolean;
85
85
  isReady: true;
86
86
  isError: boolean;
87
87
  isSuccess: boolean;
88
+ didResolve: boolean;
88
89
  }
89
- export type AsyncResult<T> = AsyncPending | AsyncReady<T>;
90
+ export type AsyncResult<T> = AsyncPending<T> | AsyncReady<T>;
90
91
  declare class StateSignal<T> implements StateSignal<T> {
91
92
  private _value;
92
93
  private _equals;
package/dist/signals.js CHANGED
@@ -3,10 +3,11 @@ let CURRENT_CONSUMER;
3
3
  let CURRENT_DEP_TAIL;
4
4
  let CURRENT_ORD = 0;
5
5
  let CURRENT_IS_WATCHED = false;
6
- let CURRENT_WAITING_STATE = false;
6
+ let CURRENT_IS_WAITING = false;
7
7
  let CURRENT_SEEN;
8
8
  let id = 0;
9
9
  const SUBSCRIPTIONS = new WeakMap();
10
+ const ACTIVE_ASYNCS = new WeakMap();
10
11
  const WAITING = Symbol();
11
12
  let linkPool;
12
13
  function linkNewDep(dep, sub, nextDep, depsTail, ord) {
@@ -114,8 +115,39 @@ export class ComputedSignal {
114
115
  this._type = type;
115
116
  this._compute = compute;
116
117
  this._equals = equals ?? ((a, b) => a === b);
117
- this._currentValue = initValue;
118
118
  this._connectedCount = type === 3 /* SignalType.Watcher */ ? 1 : 0;
119
+ this._currentValue =
120
+ type !== 2 /* SignalType.Async */
121
+ ? initValue
122
+ : {
123
+ result: initValue,
124
+ error: undefined,
125
+ isReady: initValue !== undefined,
126
+ isPending: true,
127
+ isError: false,
128
+ isSuccess: false,
129
+ didResolve: false,
130
+ invalidate: () => {
131
+ this._state = 2 /* SignalState.Dirty */;
132
+ this._dirty();
133
+ },
134
+ await: () => {
135
+ if (CURRENT_CONSUMER === undefined || CURRENT_CONSUMER._type !== 2 /* SignalType.Async */) {
136
+ throw new Error('Cannot await an async signal outside of an async signal. If you are using an async function, you must use signal.await() for all async signals _before_ the first language-level `await` keyword statement (e.g. it must be synchronous).');
137
+ }
138
+ const value = this._currentValue;
139
+ if (value.isPending) {
140
+ const currentConsumer = CURRENT_CONSUMER;
141
+ ACTIVE_ASYNCS.get(this)?.finally(() => currentConsumer._check());
142
+ CURRENT_IS_WAITING = true;
143
+ throw WAITING;
144
+ }
145
+ else if (value.isError) {
146
+ throw value.error;
147
+ }
148
+ return value.result;
149
+ },
150
+ };
119
151
  }
120
152
  get() {
121
153
  let prevTracked = false;
@@ -151,27 +183,6 @@ export class ComputedSignal {
151
183
  }
152
184
  return this._currentValue;
153
185
  }
154
- invalidate() {
155
- this._state = 2 /* SignalState.Dirty */;
156
- this._dirty();
157
- }
158
- await() {
159
- if (this._type !== 2 /* SignalType.Async */) {
160
- throw new Error('Cannot await non-async signal');
161
- }
162
- if (CURRENT_CONSUMER === undefined || CURRENT_CONSUMER._type !== 2 /* SignalType.Async */) {
163
- throw new Error('Cannot await an async signal outside of an async signal. If you are using an async function, you must use signal.await() for all async signals _before_ the first language-level `await` keyword statement (e.g. it must be synchronous).');
164
- }
165
- CURRENT_WAITING_STATE = true;
166
- const value = this.get();
167
- if (value.isPending) {
168
- throw WAITING;
169
- }
170
- else if (value.isError) {
171
- throw value.error;
172
- }
173
- return value.result;
174
- }
175
186
  _check(shouldWatch = false) {
176
187
  let state = this._state;
177
188
  let connectedCount = this._connectedCount;
@@ -246,17 +257,10 @@ export class ComputedSignal {
246
257
  break;
247
258
  }
248
259
  case 2 /* SignalType.Async */: {
249
- const value = this._currentValue ??
250
- (this._currentValue = {
251
- result: undefined,
252
- error: undefined,
253
- isPending: true,
254
- isReady: false,
255
- isError: false,
256
- isSuccess: false,
257
- });
260
+ const value = this._currentValue;
258
261
  let nextValue;
259
262
  try {
263
+ CURRENT_IS_WAITING = false;
260
264
  nextValue = this._compute(value?.result);
261
265
  }
262
266
  catch (e) {
@@ -268,16 +272,15 @@ export class ComputedSignal {
268
272
  }
269
273
  break;
270
274
  }
271
- if (typeof CURRENT_WAITING_STATE !== 'boolean') {
275
+ if (CURRENT_IS_WAITING) {
272
276
  if (!value.isPending) {
273
277
  value.isPending = true;
274
278
  value.isError = false;
275
279
  value.isSuccess = false;
276
280
  this._version++;
277
281
  }
278
- CURRENT_WAITING_STATE.finally(() => this._check());
279
282
  if (nextValue instanceof Promise) {
280
- nextValue.catch(e => {
283
+ nextValue.catch((e) => {
281
284
  if (e !== WAITING) {
282
285
  value.error = e;
283
286
  value.isPending = false;
@@ -295,6 +298,7 @@ export class ComputedSignal {
295
298
  }
296
299
  value.result = result;
297
300
  value.isReady = true;
301
+ value.didResolve = true;
298
302
  value.isPending = false;
299
303
  value.isSuccess = true;
300
304
  this._version++;
@@ -309,9 +313,7 @@ export class ComputedSignal {
309
313
  this._version++;
310
314
  this._dirtyConsumers();
311
315
  });
312
- if (CURRENT_WAITING_STATE === true) {
313
- CURRENT_WAITING_STATE = nextValue;
314
- }
316
+ ACTIVE_ASYNCS.set(this, nextValue);
315
317
  value.isPending = true;
316
318
  value.isError = false;
317
319
  value.isSuccess = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signalium",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "repository": "https://github.com/pzuraq/signalium",
6
6
  "description": "Chain-reactivity at critical mass",
@@ -8,15 +8,21 @@ const nextTick = () => new Promise(r => setTimeout(r, 0));
8
8
  const result = <T>(
9
9
  value: T | undefined,
10
10
  promiseState: 'pending' | 'error' | 'success',
11
- isReady: boolean,
11
+ readyState: 'initial' | 'ready' | 'resolved',
12
+ error?: any,
12
13
  ): AsyncResult<T> =>
13
14
  ({
14
15
  result: value,
15
- error: undefined,
16
+ error,
16
17
  isPending: promiseState === 'pending',
17
- isReady,
18
18
  isError: promiseState === 'error',
19
19
  isSuccess: promiseState === 'success',
20
+
21
+ isReady: readyState === 'ready' || readyState === 'resolved',
22
+ didResolve: readyState === 'resolved',
23
+
24
+ await: expect.any(Function),
25
+ invalidate: expect.any(Function),
20
26
  }) as AsyncResult<T>;
21
27
 
22
28
  describe('Async Signal functionality', () => {
@@ -28,20 +34,20 @@ describe('Async Signal functionality', () => {
28
34
  return a.get() + b.get();
29
35
  });
30
36
 
31
- expect(c).toHaveValueAndCounts(result(undefined, 'pending', false), {
37
+ expect(c).toHaveValueAndCounts(result(undefined, 'pending', 'initial'), {
32
38
  compute: 1,
33
39
  resolve: 0,
34
40
  });
35
41
 
36
42
  await nextTick();
37
43
 
38
- expect(c).toHaveValueAndCounts(result(3, 'success', true), {
44
+ expect(c).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
39
45
  compute: 1,
40
46
  resolve: 1,
41
47
  });
42
48
 
43
49
  // stability
44
- expect(c).toHaveValueAndCounts(result(3, 'success', true), {
50
+ expect(c).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
45
51
  compute: 1,
46
52
  resolve: 1,
47
53
  });
@@ -55,28 +61,28 @@ describe('Async Signal functionality', () => {
55
61
  return a.get() + b.get();
56
62
  });
57
63
 
58
- expect(c).toHaveValueAndCounts(result(undefined, 'pending', false), {
64
+ expect(c).toHaveValueAndCounts(result(undefined, 'pending', 'initial'), {
59
65
  compute: 1,
60
66
  resolve: 0,
61
67
  });
62
68
 
63
69
  await nextTick();
64
70
 
65
- expect(c).toHaveValueAndCounts(result(3, 'success', true), {
71
+ expect(c).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
66
72
  compute: 1,
67
73
  resolve: 1,
68
74
  });
69
75
 
70
76
  a.set(2);
71
77
 
72
- expect(c).toHaveValueAndCounts(result(3, 'pending', true), {
78
+ expect(c).toHaveValueAndCounts(result(3, 'pending', 'resolved'), {
73
79
  compute: 2,
74
80
  resolve: 1,
75
81
  });
76
82
 
77
83
  await nextTick();
78
84
 
79
- expect(c).toHaveValueAndCounts(result(4, 'success', true), {
85
+ expect(c).toHaveValueAndCounts(result(4, 'success', 'resolved'), {
80
86
  compute: 2,
81
87
  resolve: 2,
82
88
  });
@@ -90,28 +96,28 @@ describe('Async Signal functionality', () => {
90
96
  return a.get() + b.get();
91
97
  });
92
98
 
93
- expect(c).toHaveValueAndCounts(result(undefined, 'pending', false), {
99
+ expect(c).toHaveValueAndCounts(result(undefined, 'pending', 'initial'), {
94
100
  compute: 1,
95
101
  resolve: 0,
96
102
  });
97
103
 
98
104
  await nextTick();
99
105
 
100
- expect(c).toHaveValueAndCounts(result(3, 'success', true), {
106
+ expect(c).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
101
107
  compute: 1,
102
108
  resolve: 1,
103
109
  });
104
110
 
105
111
  a.set(1);
106
112
 
107
- expect(c).toHaveValueAndCounts(result(3, 'success', true), {
113
+ expect(c).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
108
114
  compute: 1,
109
115
  resolve: 1,
110
116
  });
111
117
 
112
118
  await nextTick();
113
119
 
114
- expect(c).toHaveValueAndCounts(result(3, 'success', true), {
120
+ expect(c).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
115
121
  compute: 1,
116
122
  resolve: 1,
117
123
  });
@@ -131,40 +137,77 @@ describe('Async Signal functionality', () => {
131
137
  return result;
132
138
  });
133
139
 
134
- expect(c).toHaveValueAndCounts(result(undefined, 'pending', false), {
140
+ expect(c).toHaveValueAndCounts(result(undefined, 'pending', 'initial'), {
135
141
  compute: 1,
136
142
  resolve: 0,
137
143
  });
138
144
 
139
145
  await nextTick();
140
146
 
141
- expect(c).toHaveValueAndCounts(result(3, 'success', true), {
147
+ expect(c).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
142
148
  compute: 1,
143
149
  resolve: 1,
144
150
  });
145
151
 
146
152
  a.set(2);
147
153
 
148
- expect(c).toHaveValueAndCounts(result(3, 'pending', true), {
154
+ expect(c).toHaveValueAndCounts(result(3, 'pending', 'resolved'), {
149
155
  compute: 2,
150
156
  resolve: 1,
151
157
  });
152
158
 
153
159
  a.set(3);
154
160
 
155
- expect(c).toHaveValueAndCounts(result(3, 'pending', true), {
161
+ expect(c).toHaveValueAndCounts(result(3, 'pending', 'resolved'), {
156
162
  compute: 3,
157
163
  resolve: 1,
158
164
  });
159
165
 
160
166
  await sleep(200);
161
167
 
162
- expect(c).toHaveValueAndCounts(result(5, 'success', true), {
168
+ expect(c).toHaveValueAndCounts(result(5, 'success', 'resolved'), {
163
169
  compute: 3,
164
170
  resolve: 3,
165
171
  });
166
172
  });
167
173
 
174
+ test('Can have initial value', async () => {
175
+ const a = state(1);
176
+ const b = state(2);
177
+
178
+ const c = asyncComputed(
179
+ async () => {
180
+ const result = a.get() + b.get();
181
+
182
+ await sleep(50);
183
+
184
+ return result;
185
+ },
186
+ {
187
+ initValue: 5,
188
+ },
189
+ );
190
+
191
+ expect(c).toHaveValueAndCounts(result(5, 'pending', 'ready'), {
192
+ compute: 1,
193
+ resolve: 0,
194
+ });
195
+
196
+ await nextTick();
197
+
198
+ expect(c).toHaveValueAndCounts(result(5, 'pending', 'ready'), {
199
+ compute: 1,
200
+ resolve: 0,
201
+ });
202
+
203
+ await sleep(60);
204
+
205
+ expect(c).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
206
+ compute: 1,
207
+ resolve: 1,
208
+ });
209
+ });
210
+
168
211
  describe('Awaiting', () => {
169
212
  test('Awaiting a computed will resolve the value', async () => {
170
213
  const compA = asyncComputed(async () => {
@@ -180,14 +223,14 @@ describe('Async Signal functionality', () => {
180
223
  });
181
224
 
182
225
  const compC = asyncComputed(async () => {
183
- const a = compA.await();
184
- const b = compB.await();
226
+ const a = compA.get().await();
227
+ const b = compB.get().await();
185
228
 
186
229
  return a + b;
187
230
  });
188
231
 
189
232
  // Pull once to start the computation, trigger the computation
190
- expect(compC).toHaveValueAndCounts(result(undefined, 'pending', false), {
233
+ expect(compC).toHaveValueAndCounts(result(undefined, 'pending', 'initial'), {
191
234
  compute: 1,
192
235
  resolve: 0,
193
236
  });
@@ -195,7 +238,7 @@ describe('Async Signal functionality', () => {
195
238
  await nextTick();
196
239
 
197
240
  // Check after a tick to make sure we didn't resolve early
198
- expect(compC).toHaveValueAndCounts(result(undefined, 'pending', false), {
241
+ expect(compC).toHaveValueAndCounts(result(undefined, 'pending', 'initial'), {
199
242
  compute: 1,
200
243
  resolve: 0,
201
244
  });
@@ -203,14 +246,14 @@ describe('Async Signal functionality', () => {
203
246
  await sleep(30);
204
247
 
205
248
  // Check to make sure we don't resolve early after the first task completes
206
- expect(compC).toHaveValueAndCounts(result(undefined, 'pending', false), {
249
+ expect(compC).toHaveValueAndCounts(result(undefined, 'pending', 'initial'), {
207
250
  compute: 2,
208
251
  resolve: 0,
209
252
  });
210
253
 
211
254
  await sleep(30);
212
255
 
213
- expect(compC).toHaveValueAndCounts(result(3, 'success', true), {
256
+ expect(compC).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
214
257
  compute: 3,
215
258
  resolve: 1,
216
259
  });
@@ -230,34 +273,24 @@ describe('Async Signal functionality', () => {
230
273
  });
231
274
 
232
275
  const compC = asyncComputed(async () => {
233
- const a = compA.await();
234
- const b = compB.await();
276
+ const a = compA.get().await();
277
+ const b = compB.get().await();
235
278
 
236
279
  return a + b;
237
280
  });
238
281
 
239
282
  // Pull once to start the computation, trigger the computation
240
- expect(compC).toHaveValueAndCounts(result(undefined, 'pending', false), {
283
+ expect(compC).toHaveValueAndCounts(result(undefined, 'pending', 'initial'), {
241
284
  compute: 1,
242
285
  resolve: 0,
243
286
  });
244
287
 
245
288
  await sleep(50);
246
289
 
247
- expect(compC).toHaveValueAndCounts(
248
- {
249
- result: undefined,
250
- error: 'error',
251
- isPending: false,
252
- isReady: false,
253
- isError: true,
254
- isSuccess: false,
255
- },
256
- {
257
- compute: 2,
258
- resolve: 0,
259
- },
260
- );
290
+ expect(compC).toHaveValueAndCounts(result(undefined, 'error', 'initial', 'error'), {
291
+ compute: 2,
292
+ resolve: 0,
293
+ });
261
294
  });
262
295
  });
263
296
  });
@@ -10,11 +10,9 @@ import {
10
10
  WriteableSignal,
11
11
  SignalCompute,
12
12
  SignalAsyncCompute,
13
- AsyncResult,
14
13
  SignalSubscribe,
15
14
  SignalSubscription,
16
15
  SignalOptionsWithInit,
17
- AsyncReady,
18
16
  Watcher,
19
17
  SignalWatcherEffect,
20
18
  AsyncSignal,
@@ -226,14 +224,6 @@ export function asyncComputed<T>(
226
224
  counts.get++;
227
225
  return s.get();
228
226
  },
229
-
230
- invalidate() {
231
- s.invalidate();
232
- },
233
-
234
- await() {
235
- return s.await();
236
- },
237
227
  };
238
228
 
239
229
  COUNTS.set(wrapper, counts);
package/src/signals.ts CHANGED
@@ -4,7 +4,7 @@ let CURRENT_CONSUMER: ComputedSignal<any> | undefined;
4
4
  let CURRENT_DEP_TAIL: Link | undefined;
5
5
  let CURRENT_ORD: number = 0;
6
6
  let CURRENT_IS_WATCHED: boolean = false;
7
- let CURRENT_WAITING_STATE: Promise<unknown> | boolean = false;
7
+ let CURRENT_IS_WAITING: boolean = false;
8
8
  let CURRENT_SEEN: WeakSet<ComputedSignal<any>> | undefined;
9
9
 
10
10
  let id = 0;
@@ -24,10 +24,7 @@ export interface WriteableSignal<T> extends Signal<T> {
24
24
  set(value: T): void;
25
25
  }
26
26
 
27
- export interface AsyncSignal<T> extends Signal<AsyncResult<T>> {
28
- invalidate(): void;
29
- await(): T;
30
- }
27
+ export type AsyncSignal<T> = Signal<AsyncResult<T>>;
31
28
 
32
29
  export type SignalCompute<T> = (prev: T | undefined) => T;
33
30
 
@@ -53,6 +50,7 @@ export interface SignalOptionsWithInit<T> extends SignalOptions<T> {
53
50
  }
54
51
 
55
52
  const SUBSCRIPTIONS = new WeakMap<ComputedSignal<any>, SignalSubscription | undefined | void>();
53
+ const ACTIVE_ASYNCS = new WeakMap<ComputedSignal<any>, Promise<unknown>>();
56
54
 
57
55
  const enum SignalState {
58
56
  Clean,
@@ -210,8 +208,48 @@ export class ComputedSignal<T> {
210
208
  this._type = type;
211
209
  this._compute = compute;
212
210
  this._equals = equals ?? ((a, b) => a === b);
213
- this._currentValue = initValue;
214
211
  this._connectedCount = type === SignalType.Watcher ? 1 : 0;
212
+
213
+ this._currentValue =
214
+ type !== SignalType.Async
215
+ ? initValue
216
+ : ({
217
+ result: initValue,
218
+ error: undefined,
219
+ isReady: initValue !== undefined,
220
+
221
+ isPending: true,
222
+ isError: false,
223
+ isSuccess: false,
224
+ didResolve: false,
225
+
226
+ invalidate: () => {
227
+ this._state = SignalState.Dirty;
228
+ this._dirty();
229
+ },
230
+
231
+ await: () => {
232
+ if (CURRENT_CONSUMER === undefined || CURRENT_CONSUMER._type !== SignalType.Async) {
233
+ throw new Error(
234
+ 'Cannot await an async signal outside of an async signal. If you are using an async function, you must use signal.await() for all async signals _before_ the first language-level `await` keyword statement (e.g. it must be synchronous).',
235
+ );
236
+ }
237
+
238
+ const value = this._currentValue as AsyncResult<T>;
239
+
240
+ if (value.isPending) {
241
+ const currentConsumer = CURRENT_CONSUMER;
242
+ ACTIVE_ASYNCS.get(this)?.finally(() => currentConsumer._check());
243
+
244
+ CURRENT_IS_WAITING = true;
245
+ throw WAITING;
246
+ } else if (value.isError) {
247
+ throw value.error;
248
+ }
249
+
250
+ return value.result as T;
251
+ },
252
+ } as AsyncResult<T>);
215
253
  }
216
254
 
217
255
  get(): T | AsyncResult<T> {
@@ -259,34 +297,6 @@ export class ComputedSignal<T> {
259
297
  return this._currentValue!;
260
298
  }
261
299
 
262
- invalidate() {
263
- this._state = SignalState.Dirty;
264
- this._dirty();
265
- }
266
-
267
- await() {
268
- if (this._type !== SignalType.Async) {
269
- throw new Error('Cannot await non-async signal');
270
- }
271
-
272
- if (CURRENT_CONSUMER === undefined || CURRENT_CONSUMER._type !== SignalType.Async) {
273
- throw new Error(
274
- 'Cannot await an async signal outside of an async signal. If you are using an async function, you must use signal.await() for all async signals _before_ the first language-level `await` keyword statement (e.g. it must be synchronous).',
275
- );
276
- }
277
-
278
- CURRENT_WAITING_STATE = true;
279
- const value = this.get() as AsyncResult<T>;
280
-
281
- if (value.isPending) {
282
- throw WAITING;
283
- } else if (value.isError) {
284
- throw value.error;
285
- }
286
-
287
- return value.result as T;
288
- }
289
-
290
300
  _check(shouldWatch = false): number {
291
301
  let state = this._state;
292
302
  let connectedCount = this._connectedCount;
@@ -380,20 +390,12 @@ export class ComputedSignal<T> {
380
390
  }
381
391
 
382
392
  case SignalType.Async: {
383
- const value =
384
- (this._currentValue as AsyncResult<T>) ??
385
- (this._currentValue = {
386
- result: undefined,
387
- error: undefined,
388
- isPending: true,
389
- isReady: false,
390
- isError: false,
391
- isSuccess: false,
392
- });
393
+ const value: AsyncResult<T> = this._currentValue as AsyncResult<T>;
393
394
 
394
395
  let nextValue;
395
396
 
396
397
  try {
398
+ CURRENT_IS_WAITING = false;
397
399
  nextValue = (this._compute as SignalAsyncCompute<T>)(value?.result);
398
400
  } catch (e) {
399
401
  if (e !== WAITING) {
@@ -406,7 +408,7 @@ export class ComputedSignal<T> {
406
408
  break;
407
409
  }
408
410
 
409
- if (typeof CURRENT_WAITING_STATE !== 'boolean') {
411
+ if (CURRENT_IS_WAITING) {
410
412
  if (!value.isPending) {
411
413
  value.isPending = true;
412
414
  value.isError = false;
@@ -414,10 +416,8 @@ export class ComputedSignal<T> {
414
416
  this._version++;
415
417
  }
416
418
 
417
- CURRENT_WAITING_STATE.finally(() => this._check());
418
-
419
419
  if (nextValue instanceof Promise) {
420
- nextValue.catch(e => {
420
+ nextValue.catch((e: unknown) => {
421
421
  if (e !== WAITING) {
422
422
  value.error = e;
423
423
  value.isPending = false;
@@ -437,6 +437,7 @@ export class ComputedSignal<T> {
437
437
 
438
438
  value.result = result;
439
439
  value.isReady = true;
440
+ value.didResolve = true;
440
441
 
441
442
  value.isPending = false;
442
443
  value.isSuccess = true;
@@ -457,9 +458,7 @@ export class ComputedSignal<T> {
457
458
  },
458
459
  );
459
460
 
460
- if (CURRENT_WAITING_STATE === true) {
461
- CURRENT_WAITING_STATE = nextValue;
462
- }
461
+ ACTIVE_ASYNCS.set(this, nextValue);
463
462
 
464
463
  value.isPending = true;
465
464
  value.isError = false;
@@ -635,25 +634,32 @@ export class ComputedSignal<T> {
635
634
  }
636
635
  }
637
636
 
638
- export interface AsyncPending {
637
+ export interface AsyncBaseResult<T> {
638
+ invalidate(): void;
639
+ await(): T;
640
+ }
641
+
642
+ export interface AsyncPending<T> extends AsyncBaseResult<T> {
639
643
  result: undefined;
640
644
  error: unknown;
641
645
  isPending: boolean;
642
646
  isReady: false;
643
647
  isError: boolean;
644
648
  isSuccess: boolean;
649
+ didResolve: boolean;
645
650
  }
646
651
 
647
- export interface AsyncReady<T> {
652
+ export interface AsyncReady<T> extends AsyncBaseResult<T> {
648
653
  result: T;
649
654
  error: unknown;
650
655
  isPending: boolean;
651
656
  isReady: true;
652
657
  isError: boolean;
653
658
  isSuccess: boolean;
659
+ didResolve: boolean;
654
660
  }
655
661
 
656
- export type AsyncResult<T> = AsyncPending | AsyncReady<T>;
662
+ export type AsyncResult<T> = AsyncPending<T> | AsyncReady<T>;
657
663
 
658
664
  class StateSignal<T> implements StateSignal<T> {
659
665
  private _consumers: WeakRef<ComputedSignal<unknown>>[] = [];