@praxisjs/concurrent 0.2.1 → 0.2.3
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 +14 -0
- package/dist/__tests__/decorators.test.d.ts +2 -0
- package/dist/__tests__/decorators.test.d.ts.map +1 -0
- package/dist/__tests__/decorators.test.js +93 -0
- package/dist/__tests__/decorators.test.js.map +1 -0
- package/dist/__tests__/pool.test.d.ts +2 -0
- package/dist/__tests__/pool.test.d.ts.map +1 -0
- package/dist/__tests__/pool.test.js +126 -0
- package/dist/__tests__/pool.test.js.map +1 -0
- package/dist/__tests__/queue.test.d.ts +2 -0
- package/dist/__tests__/queue.test.d.ts.map +1 -0
- package/dist/__tests__/queue.test.js +84 -0
- package/dist/__tests__/queue.test.js.map +1 -0
- package/dist/__tests__/task.test.d.ts +2 -0
- package/dist/__tests__/task.test.d.ts.map +1 -0
- package/dist/__tests__/task.test.js +113 -0
- package/dist/__tests__/task.test.js.map +1 -0
- package/package.json +2 -2
- package/src/__tests__/decorators.test.ts +119 -0
- package/src/__tests__/pool.test.ts +157 -0
- package/src/__tests__/queue.test.ts +108 -0
- package/src/__tests__/task.test.ts +137 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @praxisjs/concurrent
|
|
2
2
|
|
|
3
|
+
## 0.2.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [d11a10a]
|
|
8
|
+
- @praxisjs/core@0.4.2
|
|
9
|
+
|
|
10
|
+
## 0.2.2
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Updated dependencies [fe39901]
|
|
15
|
+
- @praxisjs/core@0.4.1
|
|
16
|
+
|
|
3
17
|
## 0.2.1
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorators.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/decorators.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { Task, Queue, Pool } from "../decorators";
|
|
3
|
+
function methodCtx(name) {
|
|
4
|
+
const initializers = [];
|
|
5
|
+
return {
|
|
6
|
+
ctx: {
|
|
7
|
+
name,
|
|
8
|
+
kind: "method",
|
|
9
|
+
addInitializer(fn) { initializers.push(fn); },
|
|
10
|
+
},
|
|
11
|
+
run(instance) { initializers.forEach((fn) => { fn.call(instance); }); },
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
// ── Task ──────────────────────────────────────────────────────────────────────
|
|
15
|
+
describe("Task decorator", () => {
|
|
16
|
+
it("replaces method with a task runner and exposes signals", async () => {
|
|
17
|
+
const { ctx, run } = methodCtx("load");
|
|
18
|
+
const original = async (x) => x * 2;
|
|
19
|
+
Task()(original, ctx);
|
|
20
|
+
const instance = {};
|
|
21
|
+
run(instance);
|
|
22
|
+
expect(typeof instance.load).toBe("function");
|
|
23
|
+
expect(instance.load_loading).toBeDefined();
|
|
24
|
+
expect(instance.load_error).toBeDefined();
|
|
25
|
+
expect(instance.load_lastResult).toBeDefined();
|
|
26
|
+
const result = await instance.load(5);
|
|
27
|
+
expect(result).toBe(10);
|
|
28
|
+
expect(instance.load_lastResult()).toBe(10);
|
|
29
|
+
});
|
|
30
|
+
it("sets loading signal while running", async () => {
|
|
31
|
+
const { ctx, run } = methodCtx("fetch");
|
|
32
|
+
let resolve;
|
|
33
|
+
const original = async () => new Promise((r) => { resolve = r; });
|
|
34
|
+
Task()(original, ctx);
|
|
35
|
+
const instance = {};
|
|
36
|
+
run(instance);
|
|
37
|
+
const p = instance.fetch();
|
|
38
|
+
expect(instance.fetch_loading()).toBe(true);
|
|
39
|
+
resolve("done");
|
|
40
|
+
await p;
|
|
41
|
+
expect(instance.fetch_loading()).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
// ── Queue ─────────────────────────────────────────────────────────────────────
|
|
45
|
+
describe("Queue decorator", () => {
|
|
46
|
+
it("replaces method with a queue runner and exposes signals", async () => {
|
|
47
|
+
const { ctx, run } = methodCtx("save");
|
|
48
|
+
const original = async (x) => String(x);
|
|
49
|
+
Queue()(original, ctx);
|
|
50
|
+
const instance = {};
|
|
51
|
+
run(instance);
|
|
52
|
+
expect(typeof instance.save).toBe("function");
|
|
53
|
+
expect(instance.save_loading).toBeDefined();
|
|
54
|
+
expect(instance.save_pending).toBeDefined();
|
|
55
|
+
expect(instance.save_error).toBeDefined();
|
|
56
|
+
const result = await instance.save("hello");
|
|
57
|
+
expect(result).toBe("hello");
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
// ── Pool ──────────────────────────────────────────────────────────────────────
|
|
61
|
+
describe("Pool decorator", () => {
|
|
62
|
+
it("replaces method with a pool runner and exposes signals", async () => {
|
|
63
|
+
const { ctx, run } = methodCtx("process");
|
|
64
|
+
const original = async (x) => x + 1;
|
|
65
|
+
Pool(2)(original, ctx);
|
|
66
|
+
const instance = {};
|
|
67
|
+
run(instance);
|
|
68
|
+
expect(typeof instance.process).toBe("function");
|
|
69
|
+
expect(instance.process_loading).toBeDefined();
|
|
70
|
+
expect(instance.process_active).toBeDefined();
|
|
71
|
+
expect(instance.process_pending).toBeDefined();
|
|
72
|
+
expect(instance.process_error).toBeDefined();
|
|
73
|
+
const result = await instance.process(9);
|
|
74
|
+
expect(result).toBe(10);
|
|
75
|
+
});
|
|
76
|
+
it("respects concurrency limit", async () => {
|
|
77
|
+
const { ctx, run } = methodCtx("work");
|
|
78
|
+
const resolvers = [];
|
|
79
|
+
const original = async () => new Promise((r) => resolvers.push(r));
|
|
80
|
+
Pool(2)(original, ctx);
|
|
81
|
+
const instance = {};
|
|
82
|
+
run(instance);
|
|
83
|
+
const work = instance.work;
|
|
84
|
+
const t1 = work();
|
|
85
|
+
const t2 = work();
|
|
86
|
+
work(); // queued — concurrency limit is 2
|
|
87
|
+
expect(instance.work_active()).toBe(2);
|
|
88
|
+
expect(instance.work_pending()).toBe(1);
|
|
89
|
+
resolvers.forEach((r) => { r(); });
|
|
90
|
+
await Promise.all([t1, t2]);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
//# sourceMappingURL=decorators.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorators.test.js","sourceRoot":"","sources":["../../src/__tests__/decorators.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/pool.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { pool } from "../pool";
|
|
3
|
+
describe("pool", () => {
|
|
4
|
+
it("executes a task and resolves with its result", async () => {
|
|
5
|
+
const p = pool(2, async (x) => x * 3);
|
|
6
|
+
expect(await p(4)).toBe(12);
|
|
7
|
+
});
|
|
8
|
+
it("respects concurrency limit", async () => {
|
|
9
|
+
let active = 0;
|
|
10
|
+
let maxActive = 0;
|
|
11
|
+
const p = pool(2, async () => {
|
|
12
|
+
active++;
|
|
13
|
+
maxActive = Math.max(maxActive, active);
|
|
14
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
15
|
+
active--;
|
|
16
|
+
});
|
|
17
|
+
await Promise.all([p(), p(), p(), p()]);
|
|
18
|
+
expect(maxActive).toBeLessThanOrEqual(2);
|
|
19
|
+
});
|
|
20
|
+
it("active() reflects currently running tasks", async () => {
|
|
21
|
+
const resolvers = [];
|
|
22
|
+
const p = pool(3, () => new Promise((r) => resolvers.push(r)));
|
|
23
|
+
const tasks = [p(), p()];
|
|
24
|
+
expect(p.active()).toBe(2);
|
|
25
|
+
resolvers.forEach((r) => { r(); });
|
|
26
|
+
await Promise.all(tasks);
|
|
27
|
+
expect(p.active()).toBe(0);
|
|
28
|
+
});
|
|
29
|
+
it("loading is true when active > 0", async () => {
|
|
30
|
+
let resolve;
|
|
31
|
+
const p = pool(1, () => new Promise((r) => { resolve = r; }));
|
|
32
|
+
const task = p();
|
|
33
|
+
expect(p.loading()).toBe(true);
|
|
34
|
+
resolve();
|
|
35
|
+
await task;
|
|
36
|
+
expect(p.loading()).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
it("captures errors in the error signal and resolves undefined", async () => {
|
|
39
|
+
const p = pool(1, async () => {
|
|
40
|
+
throw new Error("pool error");
|
|
41
|
+
});
|
|
42
|
+
const result = await p();
|
|
43
|
+
expect(result).toBeUndefined();
|
|
44
|
+
expect(p.error()?.message).toBe("pool error");
|
|
45
|
+
});
|
|
46
|
+
it("pending count decrements as tasks start", async () => {
|
|
47
|
+
let resolveFirst;
|
|
48
|
+
const started = [];
|
|
49
|
+
const p = pool(1, async (idx) => {
|
|
50
|
+
started.push(idx);
|
|
51
|
+
if (idx === 0)
|
|
52
|
+
await new Promise((r) => { resolveFirst = r; });
|
|
53
|
+
});
|
|
54
|
+
const t1 = p(0);
|
|
55
|
+
const t2 = p(1);
|
|
56
|
+
// second task is pending while first runs
|
|
57
|
+
expect(p.pending()).toBe(1);
|
|
58
|
+
resolveFirst();
|
|
59
|
+
await t1;
|
|
60
|
+
await t2;
|
|
61
|
+
expect(p.pending()).toBe(0);
|
|
62
|
+
});
|
|
63
|
+
it("wraps non-Error throws in an Error object", async () => {
|
|
64
|
+
const p = pool(1, async () => {
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
66
|
+
throw "plain string error";
|
|
67
|
+
});
|
|
68
|
+
const result = await p();
|
|
69
|
+
expect(result).toBeUndefined();
|
|
70
|
+
expect(p.error()).toBeInstanceOf(Error);
|
|
71
|
+
expect(p.error()?.message).toBe("plain string error");
|
|
72
|
+
});
|
|
73
|
+
it("concurrency=1 guarantees serial execution", async () => {
|
|
74
|
+
const order = [];
|
|
75
|
+
let resolveFirst;
|
|
76
|
+
const p = pool(1, async (n) => {
|
|
77
|
+
if (n === 0)
|
|
78
|
+
await new Promise((r) => { resolveFirst = r; });
|
|
79
|
+
order.push(n);
|
|
80
|
+
});
|
|
81
|
+
const t1 = p(0);
|
|
82
|
+
const t2 = p(1);
|
|
83
|
+
// t2 cannot start until t1 finishes
|
|
84
|
+
expect(p.active()).toBe(1);
|
|
85
|
+
expect(p.pending()).toBe(1);
|
|
86
|
+
resolveFirst();
|
|
87
|
+
await t1;
|
|
88
|
+
await t2;
|
|
89
|
+
expect(order).toEqual([0, 1]); // serial order preserved
|
|
90
|
+
});
|
|
91
|
+
it("error in one task does not block subsequent tasks", async () => {
|
|
92
|
+
const results = [];
|
|
93
|
+
const p = pool(1, async (n) => {
|
|
94
|
+
if (n === 0)
|
|
95
|
+
throw new Error("task 0 failed");
|
|
96
|
+
return n;
|
|
97
|
+
});
|
|
98
|
+
const r0 = p(0);
|
|
99
|
+
const r1 = p(1);
|
|
100
|
+
results.push(await r0);
|
|
101
|
+
results.push(await r1);
|
|
102
|
+
expect(results[0]).toBeUndefined(); // error → undefined
|
|
103
|
+
expect(results[1]).toBe(1); // subsequent task ran
|
|
104
|
+
});
|
|
105
|
+
it("concurrency=0 — tasks are enqueued but never executed", async () => {
|
|
106
|
+
const fn = vi.fn(async () => "result");
|
|
107
|
+
const p = pool(0, fn);
|
|
108
|
+
// Queue a task — tryRun() bails immediately because _active(0) >= concurrency(0)
|
|
109
|
+
void p();
|
|
110
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
111
|
+
expect(fn).not.toHaveBeenCalled();
|
|
112
|
+
expect(p.pending()).toBe(1);
|
|
113
|
+
expect(p.loading()).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
it("active() never exceeds concurrency", async () => {
|
|
116
|
+
const concurrency = 2;
|
|
117
|
+
let maxObservedActive = 0;
|
|
118
|
+
const p = pool(concurrency, async () => {
|
|
119
|
+
maxObservedActive = Math.max(maxObservedActive, p.active());
|
|
120
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
121
|
+
});
|
|
122
|
+
await Promise.all([p(), p(), p(), p()]);
|
|
123
|
+
expect(maxObservedActive).toBeLessThanOrEqual(concurrency);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
//# sourceMappingURL=pool.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.test.js","sourceRoot":"","sources":["../../src/__tests__/pool.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/queue.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { queue } from "../queue";
|
|
3
|
+
describe("queue", () => {
|
|
4
|
+
it("executes the task and resolves the promise", async () => {
|
|
5
|
+
const q = queue(async (x) => x * 2);
|
|
6
|
+
const result = await q(5);
|
|
7
|
+
expect(result).toBe(10);
|
|
8
|
+
});
|
|
9
|
+
it("runs tasks sequentially (FIFO)", async () => {
|
|
10
|
+
const order = [];
|
|
11
|
+
const q = queue(async (n) => {
|
|
12
|
+
order.push(n);
|
|
13
|
+
return n;
|
|
14
|
+
});
|
|
15
|
+
await Promise.all([q(1), q(2), q(3)]);
|
|
16
|
+
expect(order).toEqual([1, 2, 3]);
|
|
17
|
+
});
|
|
18
|
+
it("loading is true while running, false when done", async () => {
|
|
19
|
+
let resolve;
|
|
20
|
+
const q = queue(() => new Promise((r) => { resolve = r; }));
|
|
21
|
+
const p = q();
|
|
22
|
+
expect(q.loading()).toBe(true);
|
|
23
|
+
resolve();
|
|
24
|
+
await p;
|
|
25
|
+
expect(q.loading()).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
it("pending decrements as tasks complete", async () => {
|
|
28
|
+
let resolveFirst;
|
|
29
|
+
const q = queue((_, idx) => idx === 0
|
|
30
|
+
? new Promise((r) => { resolveFirst = r; })
|
|
31
|
+
: Promise.resolve());
|
|
32
|
+
const p1 = q(null, 0);
|
|
33
|
+
const p2 = q(null, 1);
|
|
34
|
+
// Both enqueued — first is running, second is pending
|
|
35
|
+
expect(q.pending()).toBeGreaterThanOrEqual(0);
|
|
36
|
+
resolveFirst();
|
|
37
|
+
await p1;
|
|
38
|
+
await p2;
|
|
39
|
+
expect(q.pending()).toBe(0);
|
|
40
|
+
});
|
|
41
|
+
it("captures errors in the error signal", async () => {
|
|
42
|
+
const q = queue(async () => {
|
|
43
|
+
throw new Error("boom");
|
|
44
|
+
});
|
|
45
|
+
await expect(q()).rejects.toThrow("boom");
|
|
46
|
+
expect(q.error()?.message).toBe("boom");
|
|
47
|
+
});
|
|
48
|
+
it("wraps non-Error throws in an Error", async () => {
|
|
49
|
+
const q = queue(async () => {
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
|
51
|
+
throw "string error";
|
|
52
|
+
});
|
|
53
|
+
await expect(q()).rejects.toThrow("string error");
|
|
54
|
+
expect(q.error()?.message).toBe("string error");
|
|
55
|
+
});
|
|
56
|
+
it("error in one task does not prevent subsequent tasks from running", async () => {
|
|
57
|
+
const q = queue(async (n) => {
|
|
58
|
+
if (n === 0)
|
|
59
|
+
throw new Error("task 0 failed");
|
|
60
|
+
return n;
|
|
61
|
+
});
|
|
62
|
+
const p0 = q(0); // will reject
|
|
63
|
+
const p1 = q(1); // should still run despite p0 failing
|
|
64
|
+
const p2 = q(2);
|
|
65
|
+
await expect(p0).rejects.toThrow("task 0 failed");
|
|
66
|
+
expect(await p1).toBe(1);
|
|
67
|
+
expect(await p2).toBe(2);
|
|
68
|
+
expect(q.error()?.message).toBe("task 0 failed");
|
|
69
|
+
});
|
|
70
|
+
it("clear() empties the pending queue", async () => {
|
|
71
|
+
let resolveFirst;
|
|
72
|
+
const q = queue((_, idx) => idx === 0
|
|
73
|
+
? new Promise((r) => { resolveFirst = r; })
|
|
74
|
+
: Promise.resolve());
|
|
75
|
+
q(null, 0);
|
|
76
|
+
q(null, 1);
|
|
77
|
+
q(null, 2);
|
|
78
|
+
q.clear();
|
|
79
|
+
resolveFirst();
|
|
80
|
+
// After clearing, pending count should be 0
|
|
81
|
+
expect(q.pending()).toBe(0);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
//# sourceMappingURL=queue.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.test.js","sourceRoot":"","sources":["../../src/__tests__/queue.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/task.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { task } from "../task";
|
|
3
|
+
describe("task", () => {
|
|
4
|
+
it("resolves with the function result", async () => {
|
|
5
|
+
const t = task(async (x) => x + 1);
|
|
6
|
+
expect(await t(9)).toBe(10);
|
|
7
|
+
});
|
|
8
|
+
it("sets loading to true while running", async () => {
|
|
9
|
+
let resolve;
|
|
10
|
+
const t = task(() => new Promise((r) => { resolve = r; }));
|
|
11
|
+
const p = t();
|
|
12
|
+
expect(t.loading()).toBe(true);
|
|
13
|
+
resolve(42);
|
|
14
|
+
await p;
|
|
15
|
+
expect(t.loading()).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
it("stores last result", async () => {
|
|
18
|
+
const t = task(async () => "hello");
|
|
19
|
+
await t();
|
|
20
|
+
expect(t.lastResult()).toBe("hello");
|
|
21
|
+
});
|
|
22
|
+
it("captures errors in the error signal", async () => {
|
|
23
|
+
const t = task(async () => {
|
|
24
|
+
throw new Error("task failed");
|
|
25
|
+
});
|
|
26
|
+
const result = await t();
|
|
27
|
+
expect(result).toBeUndefined();
|
|
28
|
+
expect(t.error()?.message).toBe("task failed");
|
|
29
|
+
});
|
|
30
|
+
it("clears error on a new run", async () => {
|
|
31
|
+
let shouldFail = true;
|
|
32
|
+
const t = task(async () => {
|
|
33
|
+
if (shouldFail)
|
|
34
|
+
throw new Error("oops");
|
|
35
|
+
return "ok";
|
|
36
|
+
});
|
|
37
|
+
await t();
|
|
38
|
+
expect(t.error()).not.toBeNull();
|
|
39
|
+
shouldFail = false;
|
|
40
|
+
await t();
|
|
41
|
+
expect(t.error()).toBeNull();
|
|
42
|
+
expect(t.lastResult()).toBe("ok");
|
|
43
|
+
});
|
|
44
|
+
it("cancelAll() discards in-flight results", async () => {
|
|
45
|
+
let resolve;
|
|
46
|
+
const t = task(() => new Promise((r) => { resolve = r; }));
|
|
47
|
+
const p = t();
|
|
48
|
+
t.cancelAll();
|
|
49
|
+
resolve("stale");
|
|
50
|
+
const result = await p;
|
|
51
|
+
expect(result).toBeUndefined();
|
|
52
|
+
expect(t.loading()).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
it("cancelAll() during a failing task discards the error", async () => {
|
|
55
|
+
let reject;
|
|
56
|
+
const t = task(() => new Promise((_res, rej) => { reject = rej; }));
|
|
57
|
+
const p = t();
|
|
58
|
+
t.cancelAll();
|
|
59
|
+
reject(new Error("cancelled"));
|
|
60
|
+
const result = await p;
|
|
61
|
+
// The error was from a stale run — result is undefined, no error stored
|
|
62
|
+
expect(result).toBeUndefined();
|
|
63
|
+
expect(t.error()).toBeNull();
|
|
64
|
+
});
|
|
65
|
+
it("wraps non-Error throws in an Error object", async () => {
|
|
66
|
+
const t = task(async () => {
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
68
|
+
throw "string-error";
|
|
69
|
+
});
|
|
70
|
+
const result = await t();
|
|
71
|
+
expect(result).toBeUndefined();
|
|
72
|
+
expect(t.error()).toBeInstanceOf(Error);
|
|
73
|
+
expect(t.error()?.message).toBe("string-error");
|
|
74
|
+
});
|
|
75
|
+
it("second run while first is pending discards the first result", async () => {
|
|
76
|
+
let resolveFirst;
|
|
77
|
+
let resolveSecond;
|
|
78
|
+
const t = task((_run) => (_run === 1
|
|
79
|
+
? new Promise((r) => { resolveFirst = r; })
|
|
80
|
+
: new Promise((r) => { resolveSecond = r; })));
|
|
81
|
+
let run = 1;
|
|
82
|
+
const p1 = t(run++); // run 1
|
|
83
|
+
const p2 = t(run++); // run 2 — increments _runId, making run 1 stale
|
|
84
|
+
resolveSecond("result-2");
|
|
85
|
+
resolveFirst("result-1"); // stale
|
|
86
|
+
const [r1, r2] = await Promise.all([p1, p2]);
|
|
87
|
+
expect(r2).toBe("result-2");
|
|
88
|
+
expect(r1).toBeUndefined(); // stale run discarded
|
|
89
|
+
expect(t.lastResult()).toBe("result-2");
|
|
90
|
+
});
|
|
91
|
+
it("synchronous function (non-async) resolves correctly via await", async () => {
|
|
92
|
+
// await on a non-Promise simply resolves the value
|
|
93
|
+
const t = task((() => 42));
|
|
94
|
+
const result = await t();
|
|
95
|
+
expect(result).toBe(42);
|
|
96
|
+
expect(t.lastResult()).toBe(42);
|
|
97
|
+
expect(t.loading()).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
it("cancelAll() followed by a new run works correctly", async () => {
|
|
100
|
+
const t = task(async () => "fresh");
|
|
101
|
+
let resolve;
|
|
102
|
+
const staleTask = task(() => new Promise((r) => { resolve = r; }));
|
|
103
|
+
const p = staleTask();
|
|
104
|
+
staleTask.cancelAll();
|
|
105
|
+
resolve("stale");
|
|
106
|
+
await p;
|
|
107
|
+
// Now run a fresh task after cancelAll
|
|
108
|
+
const result = await t();
|
|
109
|
+
expect(result).toBe("fresh");
|
|
110
|
+
expect(t.loading()).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
//# sourceMappingURL=task.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task.test.js","sourceRoot":"","sources":["../../src/__tests__/task.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praxisjs/concurrent",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"typescript": "^5.9.3"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@praxisjs/core": "0.4.
|
|
17
|
+
"@praxisjs/core": "0.4.2",
|
|
18
18
|
"@praxisjs/shared": "0.2.0"
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { Task, Queue, Pool } from "../decorators";
|
|
4
|
+
|
|
5
|
+
function methodCtx(name: string) {
|
|
6
|
+
const initializers: Array<(this: unknown) => void> = [];
|
|
7
|
+
return {
|
|
8
|
+
ctx: {
|
|
9
|
+
name,
|
|
10
|
+
kind: "method" as const,
|
|
11
|
+
addInitializer(fn: (this: unknown) => void) { initializers.push(fn); },
|
|
12
|
+
} as ClassMethodDecoratorContext,
|
|
13
|
+
run(instance: unknown) { initializers.forEach((fn) => { fn.call(instance); }); },
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ── Task ──────────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
describe("Task decorator", () => {
|
|
20
|
+
it("replaces method with a task runner and exposes signals", async () => {
|
|
21
|
+
const { ctx, run } = methodCtx("load");
|
|
22
|
+
const original = async (x: unknown) => (x as number) * 2;
|
|
23
|
+
Task()(original, ctx);
|
|
24
|
+
|
|
25
|
+
const instance: Record<string, unknown> = {};
|
|
26
|
+
run(instance);
|
|
27
|
+
|
|
28
|
+
expect(typeof instance.load).toBe("function");
|
|
29
|
+
expect(instance.load_loading).toBeDefined();
|
|
30
|
+
expect(instance.load_error).toBeDefined();
|
|
31
|
+
expect(instance.load_lastResult).toBeDefined();
|
|
32
|
+
|
|
33
|
+
const result = await (instance.load as (x: number) => Promise<number>)(5);
|
|
34
|
+
expect(result).toBe(10);
|
|
35
|
+
expect(
|
|
36
|
+
(instance.load_lastResult as () => number)(),
|
|
37
|
+
).toBe(10);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("sets loading signal while running", async () => {
|
|
41
|
+
const { ctx, run } = methodCtx("fetch");
|
|
42
|
+
let resolve!: (v: string) => void;
|
|
43
|
+
const original = async () => new Promise<string>((r) => { resolve = r; });
|
|
44
|
+
Task()(original, ctx);
|
|
45
|
+
|
|
46
|
+
const instance: Record<string, unknown> = {};
|
|
47
|
+
run(instance);
|
|
48
|
+
|
|
49
|
+
const p = (instance.fetch as () => Promise<string>)();
|
|
50
|
+
expect((instance.fetch_loading as () => boolean)()).toBe(true);
|
|
51
|
+
resolve("done");
|
|
52
|
+
await p;
|
|
53
|
+
expect((instance.fetch_loading as () => boolean)()).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// ── Queue ─────────────────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
describe("Queue decorator", () => {
|
|
60
|
+
it("replaces method with a queue runner and exposes signals", async () => {
|
|
61
|
+
const { ctx, run } = methodCtx("save");
|
|
62
|
+
const original = async (x: unknown) => String(x);
|
|
63
|
+
Queue()(original, ctx);
|
|
64
|
+
|
|
65
|
+
const instance: Record<string, unknown> = {};
|
|
66
|
+
run(instance);
|
|
67
|
+
|
|
68
|
+
expect(typeof instance.save).toBe("function");
|
|
69
|
+
expect(instance.save_loading).toBeDefined();
|
|
70
|
+
expect(instance.save_pending).toBeDefined();
|
|
71
|
+
expect(instance.save_error).toBeDefined();
|
|
72
|
+
|
|
73
|
+
const result = await (instance.save as (x: string) => Promise<string>)("hello");
|
|
74
|
+
expect(result).toBe("hello");
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// ── Pool ──────────────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
describe("Pool decorator", () => {
|
|
81
|
+
it("replaces method with a pool runner and exposes signals", async () => {
|
|
82
|
+
const { ctx, run } = methodCtx("process");
|
|
83
|
+
const original = async (x: unknown) => (x as number) + 1;
|
|
84
|
+
Pool(2)(original, ctx);
|
|
85
|
+
|
|
86
|
+
const instance: Record<string, unknown> = {};
|
|
87
|
+
run(instance);
|
|
88
|
+
|
|
89
|
+
expect(typeof instance.process).toBe("function");
|
|
90
|
+
expect(instance.process_loading).toBeDefined();
|
|
91
|
+
expect(instance.process_active).toBeDefined();
|
|
92
|
+
expect(instance.process_pending).toBeDefined();
|
|
93
|
+
expect(instance.process_error).toBeDefined();
|
|
94
|
+
|
|
95
|
+
const result = await (instance.process as (x: number) => Promise<number>)(9);
|
|
96
|
+
expect(result).toBe(10);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("respects concurrency limit", async () => {
|
|
100
|
+
const { ctx, run } = methodCtx("work");
|
|
101
|
+
const resolvers: Array<() => void> = [];
|
|
102
|
+
const original = async () => new Promise<void>((r) => resolvers.push(r));
|
|
103
|
+
Pool(2)(original, ctx);
|
|
104
|
+
|
|
105
|
+
const instance: Record<string, unknown> = {};
|
|
106
|
+
run(instance);
|
|
107
|
+
|
|
108
|
+
const work = instance.work as () => Promise<void>;
|
|
109
|
+
const t1 = work();
|
|
110
|
+
const t2 = work();
|
|
111
|
+
work(); // queued — concurrency limit is 2
|
|
112
|
+
|
|
113
|
+
expect((instance.work_active as () => number)()).toBe(2);
|
|
114
|
+
expect((instance.work_pending as () => number)()).toBe(1);
|
|
115
|
+
|
|
116
|
+
resolvers.forEach((r) => { r(); });
|
|
117
|
+
await Promise.all([t1, t2]);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { pool } from "../pool";
|
|
4
|
+
|
|
5
|
+
describe("pool", () => {
|
|
6
|
+
it("executes a task and resolves with its result", async () => {
|
|
7
|
+
const p = pool(2, async (x: unknown) => (x as number) * 3);
|
|
8
|
+
expect(await p(4)).toBe(12);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("respects concurrency limit", async () => {
|
|
12
|
+
let active = 0;
|
|
13
|
+
let maxActive = 0;
|
|
14
|
+
|
|
15
|
+
const p = pool(2, async () => {
|
|
16
|
+
active++;
|
|
17
|
+
maxActive = Math.max(maxActive, active);
|
|
18
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
19
|
+
active--;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
await Promise.all([p(), p(), p(), p()]);
|
|
23
|
+
expect(maxActive).toBeLessThanOrEqual(2);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("active() reflects currently running tasks", async () => {
|
|
27
|
+
const resolvers: Array<() => void> = [];
|
|
28
|
+
const p = pool(
|
|
29
|
+
3,
|
|
30
|
+
() => new Promise<void>((r) => resolvers.push(r)),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const tasks = [p(), p()];
|
|
34
|
+
expect(p.active()).toBe(2);
|
|
35
|
+
resolvers.forEach((r) => { r(); });
|
|
36
|
+
await Promise.all(tasks);
|
|
37
|
+
expect(p.active()).toBe(0);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("loading is true when active > 0", async () => {
|
|
41
|
+
let resolve!: () => void;
|
|
42
|
+
const p = pool(
|
|
43
|
+
1,
|
|
44
|
+
() => new Promise<void>((r) => { resolve = r; }),
|
|
45
|
+
);
|
|
46
|
+
const task = p();
|
|
47
|
+
expect(p.loading()).toBe(true);
|
|
48
|
+
resolve();
|
|
49
|
+
await task;
|
|
50
|
+
expect(p.loading()).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("captures errors in the error signal and resolves undefined", async () => {
|
|
54
|
+
const p = pool(1, async () => {
|
|
55
|
+
throw new Error("pool error");
|
|
56
|
+
});
|
|
57
|
+
const result = await p();
|
|
58
|
+
expect(result).toBeUndefined();
|
|
59
|
+
expect(p.error()?.message).toBe("pool error");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("pending count decrements as tasks start", async () => {
|
|
63
|
+
let resolveFirst!: () => void;
|
|
64
|
+
const started: number[] = [];
|
|
65
|
+
const p = pool(1, async (idx: unknown) => {
|
|
66
|
+
started.push(idx as number);
|
|
67
|
+
if (idx === 0) await new Promise<void>((r) => { resolveFirst = r; });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const t1 = p(0);
|
|
71
|
+
const t2 = p(1);
|
|
72
|
+
|
|
73
|
+
// second task is pending while first runs
|
|
74
|
+
expect(p.pending()).toBe(1);
|
|
75
|
+
resolveFirst();
|
|
76
|
+
await t1;
|
|
77
|
+
await t2;
|
|
78
|
+
expect(p.pending()).toBe(0);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("wraps non-Error throws in an Error object", async () => {
|
|
82
|
+
const p = pool(1, async () => {
|
|
83
|
+
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
84
|
+
throw "plain string error";
|
|
85
|
+
});
|
|
86
|
+
const result = await p();
|
|
87
|
+
expect(result).toBeUndefined();
|
|
88
|
+
expect(p.error()).toBeInstanceOf(Error);
|
|
89
|
+
expect(p.error()?.message).toBe("plain string error");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("concurrency=1 guarantees serial execution", async () => {
|
|
93
|
+
const order: number[] = [];
|
|
94
|
+
let resolveFirst!: () => void;
|
|
95
|
+
|
|
96
|
+
const p = pool(1, async (n: unknown) => {
|
|
97
|
+
if (n === 0) await new Promise<void>((r) => { resolveFirst = r; });
|
|
98
|
+
order.push(n as number);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const t1 = p(0);
|
|
102
|
+
const t2 = p(1);
|
|
103
|
+
|
|
104
|
+
// t2 cannot start until t1 finishes
|
|
105
|
+
expect(p.active()).toBe(1);
|
|
106
|
+
expect(p.pending()).toBe(1);
|
|
107
|
+
|
|
108
|
+
resolveFirst();
|
|
109
|
+
await t1;
|
|
110
|
+
await t2;
|
|
111
|
+
|
|
112
|
+
expect(order).toEqual([0, 1]); // serial order preserved
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("error in one task does not block subsequent tasks", async () => {
|
|
116
|
+
const results: Array<number | undefined> = [];
|
|
117
|
+
const p = pool(1, async (n: unknown) => {
|
|
118
|
+
if (n === 0) throw new Error("task 0 failed");
|
|
119
|
+
return n as number;
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const r0 = p(0);
|
|
123
|
+
const r1 = p(1);
|
|
124
|
+
|
|
125
|
+
results.push(await r0);
|
|
126
|
+
results.push(await r1);
|
|
127
|
+
|
|
128
|
+
expect(results[0]).toBeUndefined(); // error → undefined
|
|
129
|
+
expect(results[1]).toBe(1); // subsequent task ran
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("concurrency=0 — tasks are enqueued but never executed", async () => {
|
|
133
|
+
const fn = vi.fn(async () => "result");
|
|
134
|
+
const p = pool(0, fn);
|
|
135
|
+
|
|
136
|
+
// Queue a task — tryRun() bails immediately because _active(0) >= concurrency(0)
|
|
137
|
+
void p();
|
|
138
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
139
|
+
|
|
140
|
+
expect(fn).not.toHaveBeenCalled();
|
|
141
|
+
expect(p.pending()).toBe(1);
|
|
142
|
+
expect(p.loading()).toBe(false);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("active() never exceeds concurrency", async () => {
|
|
146
|
+
const concurrency = 2;
|
|
147
|
+
let maxObservedActive = 0;
|
|
148
|
+
|
|
149
|
+
const p = pool(concurrency, async () => {
|
|
150
|
+
maxObservedActive = Math.max(maxObservedActive, p.active());
|
|
151
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
await Promise.all([p(), p(), p(), p()]);
|
|
155
|
+
expect(maxObservedActive).toBeLessThanOrEqual(concurrency);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { queue } from "../queue";
|
|
4
|
+
|
|
5
|
+
describe("queue", () => {
|
|
6
|
+
it("executes the task and resolves the promise", async () => {
|
|
7
|
+
const q = queue(async (x: unknown) => (x as number) * 2);
|
|
8
|
+
const result = await q(5);
|
|
9
|
+
expect(result).toBe(10);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("runs tasks sequentially (FIFO)", async () => {
|
|
13
|
+
const order: number[] = [];
|
|
14
|
+
const q = queue(async (n: unknown) => {
|
|
15
|
+
order.push(n as number);
|
|
16
|
+
return n as number;
|
|
17
|
+
});
|
|
18
|
+
await Promise.all([q(1), q(2), q(3)]);
|
|
19
|
+
expect(order).toEqual([1, 2, 3]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("loading is true while running, false when done", async () => {
|
|
23
|
+
let resolve!: () => void;
|
|
24
|
+
const q = queue(
|
|
25
|
+
() => new Promise<void>((r) => { resolve = r; }),
|
|
26
|
+
);
|
|
27
|
+
const p = q();
|
|
28
|
+
expect(q.loading()).toBe(true);
|
|
29
|
+
resolve();
|
|
30
|
+
await p;
|
|
31
|
+
expect(q.loading()).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("pending decrements as tasks complete", async () => {
|
|
35
|
+
let resolveFirst!: () => void;
|
|
36
|
+
const q = queue(
|
|
37
|
+
(_: unknown, idx: unknown) =>
|
|
38
|
+
idx === 0
|
|
39
|
+
? new Promise<void>((r) => { resolveFirst = r; })
|
|
40
|
+
: Promise.resolve(),
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const p1 = q(null, 0);
|
|
44
|
+
const p2 = q(null, 1);
|
|
45
|
+
|
|
46
|
+
// Both enqueued — first is running, second is pending
|
|
47
|
+
expect(q.pending()).toBeGreaterThanOrEqual(0);
|
|
48
|
+
|
|
49
|
+
resolveFirst();
|
|
50
|
+
await p1;
|
|
51
|
+
await p2;
|
|
52
|
+
|
|
53
|
+
expect(q.pending()).toBe(0);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("captures errors in the error signal", async () => {
|
|
57
|
+
const q = queue(async () => {
|
|
58
|
+
throw new Error("boom");
|
|
59
|
+
});
|
|
60
|
+
await expect(q()).rejects.toThrow("boom");
|
|
61
|
+
expect(q.error()?.message).toBe("boom");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("wraps non-Error throws in an Error", async () => {
|
|
65
|
+
const q = queue(async () => {
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
|
67
|
+
throw "string error";
|
|
68
|
+
});
|
|
69
|
+
await expect(q()).rejects.toThrow("string error");
|
|
70
|
+
expect(q.error()?.message).toBe("string error");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("error in one task does not prevent subsequent tasks from running", async () => {
|
|
74
|
+
const q = queue(async (n: unknown) => {
|
|
75
|
+
if (n === 0) throw new Error("task 0 failed");
|
|
76
|
+
return n as number;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const p0 = q(0); // will reject
|
|
80
|
+
const p1 = q(1); // should still run despite p0 failing
|
|
81
|
+
const p2 = q(2);
|
|
82
|
+
|
|
83
|
+
await expect(p0).rejects.toThrow("task 0 failed");
|
|
84
|
+
expect(await p1).toBe(1);
|
|
85
|
+
expect(await p2).toBe(2);
|
|
86
|
+
expect(q.error()?.message).toBe("task 0 failed");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("clear() empties the pending queue", async () => {
|
|
90
|
+
let resolveFirst!: () => void;
|
|
91
|
+
const q = queue(
|
|
92
|
+
(_: unknown, idx: unknown) =>
|
|
93
|
+
idx === 0
|
|
94
|
+
? new Promise<void>((r) => { resolveFirst = r; })
|
|
95
|
+
: Promise.resolve(),
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
q(null, 0);
|
|
99
|
+
q(null, 1);
|
|
100
|
+
q(null, 2);
|
|
101
|
+
|
|
102
|
+
q.clear();
|
|
103
|
+
resolveFirst();
|
|
104
|
+
|
|
105
|
+
// After clearing, pending count should be 0
|
|
106
|
+
expect(q.pending()).toBe(0);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { task } from "../task";
|
|
4
|
+
|
|
5
|
+
describe("task", () => {
|
|
6
|
+
it("resolves with the function result", async () => {
|
|
7
|
+
const t = task(async (x: unknown) => (x as number) + 1);
|
|
8
|
+
expect(await t(9)).toBe(10);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("sets loading to true while running", async () => {
|
|
12
|
+
let resolve!: (v: number) => void;
|
|
13
|
+
const t = task(() => new Promise<number>((r) => { resolve = r; }));
|
|
14
|
+
|
|
15
|
+
const p = t();
|
|
16
|
+
expect(t.loading()).toBe(true);
|
|
17
|
+
resolve(42);
|
|
18
|
+
await p;
|
|
19
|
+
expect(t.loading()).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("stores last result", async () => {
|
|
23
|
+
const t = task(async () => "hello");
|
|
24
|
+
await t();
|
|
25
|
+
expect(t.lastResult()).toBe("hello");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("captures errors in the error signal", async () => {
|
|
29
|
+
const t = task(async () => {
|
|
30
|
+
throw new Error("task failed");
|
|
31
|
+
});
|
|
32
|
+
const result = await t();
|
|
33
|
+
expect(result).toBeUndefined();
|
|
34
|
+
expect(t.error()?.message).toBe("task failed");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("clears error on a new run", async () => {
|
|
38
|
+
let shouldFail = true;
|
|
39
|
+
const t = task(async () => {
|
|
40
|
+
if (shouldFail) throw new Error("oops");
|
|
41
|
+
return "ok";
|
|
42
|
+
});
|
|
43
|
+
await t();
|
|
44
|
+
expect(t.error()).not.toBeNull();
|
|
45
|
+
|
|
46
|
+
shouldFail = false;
|
|
47
|
+
await t();
|
|
48
|
+
expect(t.error()).toBeNull();
|
|
49
|
+
expect(t.lastResult()).toBe("ok");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("cancelAll() discards in-flight results", async () => {
|
|
53
|
+
let resolve!: (v: string) => void;
|
|
54
|
+
const t = task(() => new Promise<string>((r) => { resolve = r; }));
|
|
55
|
+
|
|
56
|
+
const p = t();
|
|
57
|
+
t.cancelAll();
|
|
58
|
+
resolve("stale");
|
|
59
|
+
const result = await p;
|
|
60
|
+
|
|
61
|
+
expect(result).toBeUndefined();
|
|
62
|
+
expect(t.loading()).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("cancelAll() during a failing task discards the error", async () => {
|
|
66
|
+
let reject!: (err: unknown) => void;
|
|
67
|
+
const t = task(() => new Promise<string>((_res, rej) => { reject = rej; }));
|
|
68
|
+
|
|
69
|
+
const p = t();
|
|
70
|
+
t.cancelAll();
|
|
71
|
+
reject(new Error("cancelled"));
|
|
72
|
+
const result = await p;
|
|
73
|
+
|
|
74
|
+
// The error was from a stale run — result is undefined, no error stored
|
|
75
|
+
expect(result).toBeUndefined();
|
|
76
|
+
expect(t.error()).toBeNull();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("wraps non-Error throws in an Error object", async () => {
|
|
80
|
+
const t = task(async () => {
|
|
81
|
+
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
82
|
+
throw "string-error";
|
|
83
|
+
});
|
|
84
|
+
const result = await t();
|
|
85
|
+
expect(result).toBeUndefined();
|
|
86
|
+
expect(t.error()).toBeInstanceOf(Error);
|
|
87
|
+
expect(t.error()?.message).toBe("string-error");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("second run while first is pending discards the first result", async () => {
|
|
91
|
+
let resolveFirst!: (v: string) => void;
|
|
92
|
+
let resolveSecond!: (v: string) => void;
|
|
93
|
+
|
|
94
|
+
const t = task((_run: unknown) =>
|
|
95
|
+
(_run === 1
|
|
96
|
+
? new Promise<string>((r) => { resolveFirst = r; })
|
|
97
|
+
: new Promise<string>((r) => { resolveSecond = r; })),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
let run = 1;
|
|
101
|
+
const p1 = t(run++); // run 1
|
|
102
|
+
const p2 = t(run++); // run 2 — increments _runId, making run 1 stale
|
|
103
|
+
|
|
104
|
+
resolveSecond("result-2");
|
|
105
|
+
resolveFirst("result-1"); // stale
|
|
106
|
+
|
|
107
|
+
const [r1, r2] = await Promise.all([p1, p2]);
|
|
108
|
+
expect(r2).toBe("result-2");
|
|
109
|
+
expect(r1).toBeUndefined(); // stale run discarded
|
|
110
|
+
expect(t.lastResult()).toBe("result-2");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("synchronous function (non-async) resolves correctly via await", async () => {
|
|
114
|
+
// await on a non-Promise simply resolves the value
|
|
115
|
+
const t = task((() => 42) as unknown as () => Promise<number>);
|
|
116
|
+
const result = await t();
|
|
117
|
+
expect(result).toBe(42);
|
|
118
|
+
expect(t.lastResult()).toBe(42);
|
|
119
|
+
expect(t.loading()).toBe(false);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("cancelAll() followed by a new run works correctly", async () => {
|
|
123
|
+
const t = task(async () => "fresh");
|
|
124
|
+
let resolve!: (v: string) => void;
|
|
125
|
+
const staleTask = task(() => new Promise<string>((r) => { resolve = r; }));
|
|
126
|
+
|
|
127
|
+
const p = staleTask();
|
|
128
|
+
staleTask.cancelAll();
|
|
129
|
+
resolve("stale");
|
|
130
|
+
await p;
|
|
131
|
+
|
|
132
|
+
// Now run a fresh task after cancelAll
|
|
133
|
+
const result = await t();
|
|
134
|
+
expect(result).toBe("fresh");
|
|
135
|
+
expect(t.loading()).toBe(false);
|
|
136
|
+
});
|
|
137
|
+
});
|