/
deferred.ts
136 lines (121 loc) · 3.64 KB
/
deferred.ts
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import { type Deferred, deferred } from "../deps.ts";
export class DeferredStack<T> {
#elements: Array<T>;
#creator?: () => Promise<T>;
#max_size: number;
#queue: Array<Deferred<T>>;
#size: number;
constructor(
max?: number,
ls?: Iterable<T>,
creator?: () => Promise<T>,
) {
this.#elements = ls ? [...ls] : [];
this.#creator = creator;
this.#max_size = max || 10;
this.#queue = [];
this.#size = this.#elements.length;
}
get available(): number {
return this.#elements.length;
}
async pop(): Promise<T> {
if (this.#elements.length > 0) {
return this.#elements.pop()!;
} else if (this.#size < this.#max_size && this.#creator) {
this.#size++;
return await this.#creator();
}
const d = deferred<T>();
this.#queue.push(d);
return await d;
}
push(value: T): void {
if (this.#queue.length > 0) {
const d = this.#queue.shift()!;
d.resolve(value);
} else {
this.#elements.push(value);
}
}
get size(): number {
return this.#size;
}
}
/**
* The DeferredAccessStack provides access to a series of elements provided on the stack creation,
* but with the caveat that they require an initialization of sorts before they can be used
*
* Instead of providing a `creator` function as you would with the `DeferredStack`, you provide
* an initialization callback to execute for each element that is retrieved from the stack and a check
* callback to determine if the element requires initialization and return a count of the initialized
* elements
*/
export class DeferredAccessStack<T> {
#elements: Array<T>;
#initializeElement: (element: T) => Promise<void>;
#checkElementInitialization: (element: T) => Promise<boolean> | boolean;
#queue: Array<Deferred<T>>;
#size: number;
get available(): number {
return this.#elements.length;
}
/**
* The max number of elements that can be contained in the stack a time
*/
get size(): number {
return this.#size;
}
/**
* @param initialize This function will execute for each element that hasn't been initialized when requested from the stack
*/
constructor(
elements: T[],
initCallback: (element: T) => Promise<void>,
checkInitCallback: (element: T) => Promise<boolean> | boolean,
) {
this.#checkElementInitialization = checkInitCallback;
this.#elements = elements;
this.#initializeElement = initCallback;
this.#queue = [];
this.#size = elements.length;
}
/**
* Will execute the check for initialization on each element of the stack
* and then return the number of initialized elements that pass the check
*/
async initialized(): Promise<number> {
const initialized = await Promise.all(
this.#elements.map((e) => this.#checkElementInitialization(e)),
);
return initialized
.filter((initialized) => initialized === true)
.length;
}
async pop(): Promise<T> {
let element: T;
if (this.available > 0) {
element = this.#elements.pop()!;
} else {
// If there are not elements left in the stack, it will await the call until
// at least one is restored and then return it
const d = deferred<T>();
this.#queue.push(d);
element = await d;
}
if (!await this.#checkElementInitialization(element)) {
await this.#initializeElement(element);
}
return element;
}
push(value: T): void {
// If an element has been requested while the stack was empty, indicate
// that an element has been restored
if (this.#queue.length > 0) {
const d = this.#queue.shift()!;
d.resolve(value);
} else {
this.#elements.push(value);
}
}
}