-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathasyncGenerator.js
More file actions
109 lines (97 loc) · 3.21 KB
/
asyncGenerator.js
File metadata and controls
109 lines (97 loc) · 3.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import { Queue } from './Queue.js';
/**
* Creates an asynchronous generator with the ability to resolve promises externally.
*
* @param {Object} [options] - Options for the generator.
* @param {AbortSignal} [options.signal] - An optional AbortSignal to handle abortion.
* @returns {Object} - An object containing the resolve function and the asynchronous generator.
* @property {Function} resolve - The function to resolve promises and advance the generator.
* @property {Function} reject - The function to reject promises.
* @property {AsyncGenerator} generator - The asynchronous generator.
*/
export function getGeneratorWithResolvers({ signal: givenSignal } = {}) {
const queue = new Queue();
const controller = new AbortController();
const createDeferred = () => ({ ...Promise.withResolvers(), resolved: false });
const signal = givenSignal instanceof AbortSignal
? AbortSignal.any([givenSignal, controller.signal])
: controller.signal;
let current = createDeferred();
const getNext = () => {
if (queue.isEmpty()) {
return createDeferred();
} else {
queue.enqueue(createDeferred());
return queue.dequeue();
}
};
/**
* Resolves a promise, advancing the asynchronous generator with the provided result.
*
* @param {*} result - The result to resolve the promise with.
* @returns {void}
*/
const resolve = result => {
if (! current.resolved) {
current.resolve(result);
current.resolved = true;
} else if (queue.isEmpty() || queue.rear.resolved) {
queue.enqueue({ promise: Promise.resolve(result), resolved: true });
} else {
queue.rear.resolve(result);
queue.rear.resolved = true;
}
};
const reject = error => {
if (! current.resolved) {
current.reject(error);
current.resolved = true;
} else if (queue.isEmpty() || queue.rear.resolved) {
queue.enqueue({ promise: Promise.reject(error), resolved: true });
} else {
queue.rear.resolve(error);
queue.rear.resolved = true;
}
};
/**
* Asynchronous generator function that yields resolved promises and handles an optional AbortSignal.
*
* @generator
* @yields {*} The result of each resolved promise.
*/
const generator = (async function *generator() {
const ABORTED = Symbol('aborted');
const { resolve, promise: aborted } = Promise.withResolvers();
if (signal.aborted) {
resolve(ABORTED);
return aborted;
} else {
signal.addEventListener('abort', () => resolve(ABORTED), { once: true });
}
while (! signal.aborted) {
const result = await Promise.race([current.promise, aborted]);
if (result === ABORTED) {
return undefined;
} else {
yield result;
current = getNext();
}
}
})();
return Object.freeze({ resolve, reject, generator, controller: Object.freeze({
abort(reason) {
controller.abort(reason);
},
get signal() {
return signal;
},
})});
}
export async function* eventGenerator(target, event, { signal, passive, capture } = {}) {
const { resolve, generator } = getGeneratorWithResolvers({ signal });
target.addEventListener(event, resolve, { signal, passive, capture });
yield* generator;
}
export async function* clickGenerator(target, { signal, passive, capture } = {}) {
yield* eventGenerator(target, 'click', { signal, passive, capture });
}