Skip to content

Commit 0478526

Browse files
feat(delay): Allow passing array of targets to delay (#288)
Co-authored-by: Alexander Khoroshikh <[email protected]>
1 parent 6bce049 commit 0478526

File tree

4 files changed

+222
-29
lines changed

4 files changed

+222
-29
lines changed

src/delay/delay.test.ts

+46
Original file line numberDiff line numberDiff line change
@@ -234,3 +234,49 @@ test('double delay effect', async () => {
234234
]
235235
`);
236236
});
237+
238+
test('delay with array of units', async () => {
239+
expect.assertions(2);
240+
241+
const source = createEvent();
242+
243+
const fnA = jest.fn();
244+
const targetA = createEvent();
245+
246+
targetA.watch(fnA);
247+
248+
const fnB = jest.fn();
249+
const targetB = createEvent();
250+
251+
targetB.watch(fnB);
252+
253+
delay({ source, timeout: 100, target: [targetA, targetB] });
254+
255+
source(1);
256+
257+
await waitFor(targetA);
258+
259+
expect(fnA).toHaveBeenCalledTimes(1);
260+
expect(fnB).toHaveBeenCalledTimes(1);
261+
});
262+
263+
test('delay throws when any of targets is not a unit', async () => {
264+
expect.assertions(1);
265+
266+
const source = createEvent();
267+
const target = createEvent();
268+
269+
expect(() =>
270+
delay({ source, timeout: 100, target: [target, 'not a unit'] }),
271+
).toThrowError(/target must be a unit/);
272+
});
273+
274+
test('delay throws when source is not a unit', async () => {
275+
expect.assertions(1);
276+
277+
const target = createEvent();
278+
279+
expect(() => delay({ source: 'not a unit', timeout: 100, target })).toThrowError(
280+
/source must be a unit/,
281+
);
282+
});

src/delay/index.ts

+36-22
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,54 @@
11
import {
22
createEffect,
33
createEvent,
4-
forward,
54
is,
65
sample,
76
Unit,
8-
Event,
97
Store,
10-
Effect,
8+
EventAsReturnType,
119
combine,
10+
Target as TargetType,
11+
MultiTarget,
12+
UnitValue,
1213
} from 'effector';
1314

14-
type EventAsReturnType<Payload> = any extends Payload ? Event<Payload> : never;
15+
type TimeoutType<Payload> = ((payload: Payload) => number) | Store<number> | number;
1516

16-
export function delay<T>({
17+
export function delay<Source extends Unit<any>, Target extends TargetType>(config: {
18+
source: Source;
19+
timeout: TimeoutType<UnitValue<Source>>;
20+
target: MultiTarget<Target, UnitValue<Source>>;
21+
}): Target;
22+
23+
export function delay<Source extends Unit<any>>(config: {
24+
source: Source;
25+
timeout: TimeoutType<UnitValue<Source>>;
26+
}): EventAsReturnType<UnitValue<Source>>;
27+
28+
export function delay<
29+
Source extends Unit<any>,
30+
Target extends TargetType = TargetType,
31+
>({
1732
source,
1833
timeout,
19-
target = createEvent<T>(),
34+
target = createEvent() as any,
2035
}: {
21-
source: Unit<T>;
22-
timeout: ((_payload: T) => number) | Store<number> | number;
23-
target?:
24-
| Store<T>
25-
| Event<T>
26-
| Effect<T, any, any>
27-
| Event<void>
28-
| Effect<void, any, any>;
29-
}): EventAsReturnType<T> {
30-
if (!is.unit(source)) throw new TypeError('source must be a unit from effector');
36+
source: Source;
37+
timeout: TimeoutType<UnitValue<Source>>;
38+
target?: MultiTarget<Target, UnitValue<Source>>;
39+
}): typeof target extends undefined ? EventAsReturnType<UnitValue<Source>> : Target {
40+
const targets = Array.isArray(target) ? target : [target];
3141

32-
if (!is.unit(target)) throw new TypeError('target must be a unit from effector');
42+
if (!is.unit(source)) throw new TypeError('source must be a unit from effector');
43+
if (!targets.every((unit) => is.unit(unit)))
44+
throw new TypeError('target must be a unit from effector');
3345

3446
const ms = validateTimeout(timeout);
3547

36-
const timerFx = createEffect<{ payload: T; milliseconds: number }, T>(
48+
const timerFx = createEffect<
49+
{ payload: UnitValue<Source>; milliseconds: number },
50+
UnitValue<Source>
51+
>(
3752
({ payload, milliseconds }) =>
3853
new Promise((resolve) => {
3954
setTimeout(resolve, milliseconds, payload);
@@ -44,7 +59,7 @@ export function delay<T>({
4459
// ms can be Store<number> | number
4560
// converts object of stores or object of values to store
4661
source: combine({ milliseconds: ms }),
47-
clock: source,
62+
clock: source as Unit<any>,
4863
fn: ({ milliseconds }, payload) => ({
4964
payload,
5065
milliseconds:
@@ -53,10 +68,9 @@ export function delay<T>({
5368
target: timerFx,
5469
});
5570

56-
// @ts-expect-error
57-
forward({ from: timerFx.doneData, to: target });
71+
sample({ clock: timerFx.doneData, target: targets as Unit<any>[] });
5872

59-
return timerFx.doneData;
73+
return target as any;
6074
}
6175

6276
function validateTimeout<T>(

src/delay/readme.md

+12-4
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ target = delay({ source, timeout: number, target });
2020

2121
1. `source` `(Event<T>` | `Store<T>` | `Effect<T>)` — Source unit, data from this unit used to trigger `target` with.
2222
1. `timeout` `(number)` — time to wait before trigger `event`
23-
1. `target` `(Event<T>` | `Store<T>` | `Effect<T`>)` — Optional. Target unit, that should be called after delay.
23+
1. `target` `(Unit<T>` | `Array<Unit<T>>)` — Optional. Target unit or array of units that will be called after delay.
2424

2525
### Returns
2626

27-
- `event` `(Event<T>)`New event, that triggered after delay
27+
- `target` `(Unit<T>` | `Array<Unit<T>>)`Target unit or units that were passed to `delay`
2828

2929
### Example
3030

@@ -61,7 +61,11 @@ target = delay({ source, timeout: Function, target });
6161

6262
1. `source` `(Event<T>` | `Store<T>` | `Effect<T>)` — Source unit, data from this unit used to trigger `target` with.
6363
1. `timeout` `((payload: T) => number)` — Calculate delay for each `source` call. Receives the payload of `source` as argument. Should return `number` — delay in milliseconds.
64-
1. `target` `(Event<T>` | `Store<T>` | `Effect<T`>)` — Optional. Target unit, that should be called after delay.
64+
1. `target` `(Unit<T>` | `Array<Unit<T>>)` — Optional. Target unit or array of units that will be called after delay.
65+
66+
### Returns
67+
68+
- `target` `(Unit<T>` | `Array<Unit<T>>)` — Target unit or units that were passed to `delay`
6569

6670
### Example
6771

@@ -109,7 +113,11 @@ target = delay({ source, timeout: $store, target });
109113

110114
1. `source` `(Event<T>` | `Store<T>` | `Effect<T>)` — Source unit, data from this unit used to trigger `target` with.
111115
1. `timeout` `(Store<number>)` — Store with number — delay in milliseconds.
112-
1. `target` `(Event<T>` | `Store<T>` | `Effect<T`>)` — Optional. Target unit, that should be called after delay.
116+
1. `target` `(Unit<T>` | `Array<Unit<T>>)` — Optional. Target unit or array of units that will be called after delay.
117+
118+
### Returns
119+
120+
- `target` `(Unit<T>` | `Array<Unit<T>>)` — Target unit or units that were passed to `delay`
113121

114122
### Example
115123

test-typings/delay.ts

+128-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expectType } from 'tsd';
2-
import { Event, createStore, createEvent, createEffect } from 'effector';
2+
import { Event, createStore, createEvent, createEffect, Store } from 'effector';
33
import { delay } from '../src/delay';
44

55
// Check valid type for source
@@ -90,13 +90,138 @@ import { delay } from '../src/delay';
9090
const source = createEvent<number>();
9191
const target = createEvent();
9292

93-
expectType<Event<number>>(delay({ source, timeout: 100, target }));
93+
expectType<typeof target>(delay({ source, timeout: 100, target }));
9494
}
9595

9696
// void effects support
9797
{
9898
const source = createEvent<number>();
9999
const target = createEffect<void, void>();
100100

101-
expectType<Event<number>>(delay({ source, timeout: 100, target }));
101+
expectType<typeof target>(delay({ source, timeout: 100, target }));
102+
}
103+
104+
// supports wider type in target
105+
{
106+
const source = createEvent<number>();
107+
const target = createEvent<number | string>();
108+
109+
delay({ source, timeout: 100, target });
110+
}
111+
112+
// does not allow narrower type in target
113+
{
114+
const source = createEvent<number>();
115+
const target = createEvent<1 | 2>();
116+
117+
delay({
118+
source,
119+
timeout: 100,
120+
// @ts-expect-error
121+
target,
122+
});
123+
}
124+
125+
// supports multiple targets as an array
126+
{
127+
const source = createStore<string>('');
128+
129+
const $targetStore = createStore<string>('');
130+
131+
const targetEvent = createEvent<string>();
132+
const targetEventVoid = createEvent<void>();
133+
134+
const targetEffect = createEffect<string, void>();
135+
const targetEffectVoid = createEffect<void, void>();
136+
137+
delay({
138+
source,
139+
timeout: 100,
140+
target: [
141+
$targetStore,
142+
targetEvent,
143+
targetEventVoid,
144+
targetEffect,
145+
targetEffectVoid,
146+
],
147+
});
148+
}
149+
150+
// does not allow invalid targets in array
151+
{
152+
const source = createStore<string>('');
153+
154+
// @ts-expect-error
155+
delay({ source, timeout: 100, target: ['non-unit'] });
156+
// @ts-expect-error
157+
delay({ source, timeout: 100, target: [null] });
158+
// @ts-expect-error
159+
delay({ source, timeout: 100, target: [100] });
160+
// @ts-expect-error
161+
delay({ source, timeout: 100, target: [() => ''] });
162+
}
163+
164+
// does not allow incompatible targets in array
165+
{
166+
const source = createStore<string>('');
167+
168+
// @ts-expect-error
169+
delay({
170+
source,
171+
timeout: 100,
172+
target: [createEvent<number>()],
173+
});
174+
175+
// @ts-expect-error
176+
delay({
177+
source,
178+
timeout: 100,
179+
target: [createEffect<number, void>()],
180+
});
181+
182+
// @ts-expect-error
183+
delay({
184+
source,
185+
timeout: 100,
186+
target: [createStore<number>(0)],
187+
});
188+
189+
delay({
190+
source,
191+
timeout: 100,
192+
// @ts-expect-error
193+
target: [
194+
createEvent<number>(),
195+
createEffect<number, void>(),
196+
createStore<number>(0),
197+
],
198+
});
199+
200+
// @ts-expect-error
201+
delay({
202+
source,
203+
timeout: 100,
204+
target: [createEvent<string>(), 'non-unit'],
205+
});
206+
}
207+
208+
// returns typeof target
209+
{
210+
const source = createStore<'x'>('x');
211+
212+
expectType<Event<'x'>>(
213+
delay({
214+
source,
215+
timeout: 100,
216+
target: createEvent<'x'>(),
217+
}),
218+
);
219+
220+
expectType<[Event<'x'>, Store<string>, Event<string>]>(
221+
delay({
222+
source,
223+
timeout: 100,
224+
target: [createEvent<'x'>(), createStore<string>(''), createEvent<string>()],
225+
}),
226+
);
102227
}

0 commit comments

Comments
 (0)