UNPKG

6.12 kBJavaScriptView Raw
1/**
2 * @experimental Use of this module is not encouraged!
3 * This is just an experiment.
4 * @todo remove `c8 ignore` line once this is moved to "non-experimental"
5 */
6
7import * as queue from './queue.js'
8import * as object from './object.js'
9
10/* c8 ignore start */
11
12/**
13 * @type {queue.Queue<queue.QueueValue<()=>void>>}
14 */
15const ctxFs = queue.create()
16
17/**
18 * @param {() => void} f
19 */
20const runInGlobalContext = f => {
21 const isEmpty = queue.isEmpty(ctxFs)
22 queue.enqueue(ctxFs, new queue.QueueValue(f))
23 if (isEmpty) {
24 while (!queue.isEmpty(ctxFs)) {
25 /** @type {queue.QueueValue<()=>{}>} */ (ctxFs.start).v()
26 queue.dequeue(ctxFs)
27 }
28 }
29}
30
31/**
32 * @template V
33 * @typedef {V | PledgeInstance<V>} Pledge
34 */
35
36/**
37 * @template {any} Val
38 * @template {any} [CancelReason=Error]
39 */
40export class PledgeInstance {
41 constructor () {
42 /**
43 * @type {Val | CancelReason | null}
44 */
45 this._v = null
46 this.isResolved = false
47 /**
48 * @type {Array<function(Val):void> | null}
49 */
50 this._whenResolved = []
51 /**
52 * @type {Array<function(CancelReason):void> | null}
53 */
54 this._whenCanceled = []
55 }
56
57 get isDone () {
58 return this._whenResolved === null
59 }
60
61 get isCanceled () {
62 return !this.isResolved && this._whenResolved === null
63 }
64
65 /**
66 * @param {Val} v
67 */
68 resolve (v) {
69 const whenResolved = this._whenResolved
70 if (whenResolved === null) return
71 this._v = v
72 this.isResolved = true
73 this._whenResolved = null
74 this._whenCanceled = null
75 for (let i = 0; i < whenResolved.length; i++) {
76 whenResolved[i](v)
77 }
78 }
79
80 /**
81 * @param {CancelReason} reason
82 */
83 cancel (reason) {
84 const whenCanceled = this._whenCanceled
85 if (whenCanceled === null) return
86 this._v = reason
87 this._whenResolved = null
88 this._whenCanceled = null
89 for (let i = 0; i < whenCanceled.length; i++) {
90 whenCanceled[i](reason)
91 }
92 }
93
94 /**
95 * @template R
96 * @param {function(Val):Pledge<R>} f
97 * @return {PledgeInstance<R>}
98 */
99 map (f) {
100 /**
101 * @type {PledgeInstance<R>}
102 */
103 const p = new PledgeInstance()
104 this.whenResolved(v => {
105 const result = f(v)
106 if (result instanceof PledgeInstance) {
107 if (result._whenResolved === null) {
108 result.resolve(/** @type {R} */ (result._v))
109 } else {
110 result._whenResolved.push(p.resolve.bind(p))
111 }
112 } else {
113 p.resolve(result)
114 }
115 })
116 return p
117 }
118
119 /**
120 * @param {function(Val):void} f
121 */
122 whenResolved (f) {
123 if (this.isResolved) {
124 f(/** @type {Val} */ (this._v))
125 } else {
126 this._whenResolved?.push(f)
127 }
128 }
129
130 /**
131 * @param {(reason: CancelReason) => void} f
132 */
133 whenCanceled (f) {
134 if (this.isCanceled) {
135 f(/** @type {CancelReason} */ (this._v))
136 } else {
137 this._whenCanceled?.push(f)
138 }
139 }
140
141 /**
142 * @return {Promise<Val>}
143 */
144 promise () {
145 return new Promise((resolve, reject) => {
146 this.whenResolved(resolve)
147 this.whenCanceled(reject)
148 })
149 }
150}
151
152/**
153 * @template T
154 * @return {PledgeInstance<T>}
155 */
156export const create = () => new PledgeInstance()
157
158/**
159 * @typedef {Array<Pledge<unknown>> | Object<string,Pledge<unknown>>} PledgeMap
160 */
161
162/**
163 * @template {Pledge<unknown> | PledgeMap} P
164 * @typedef {P extends PledgeMap ? { [K in keyof P]: P[K] extends Pledge<infer V> ? V : P[K]} : (P extends Pledge<infer V> ? V : never)} Resolved<P>
165 */
166
167/**
168 * @todo Create a "resolveHelper" that will simplify creating indxeddbv2 functions. Double arguments
169 * are not necessary.
170 *
171 * @template V
172 * @template {Array<Pledge<unknown>>} DEPS
173 * @param {(p: PledgeInstance<V>, ...deps: Resolved<DEPS>) => void} init
174 * @param {DEPS} deps
175 * @return {PledgeInstance<V>}
176 */
177export const createWithDependencies = (init, ...deps) => {
178 /**
179 * @type {PledgeInstance<V>}
180 */
181 const p = new PledgeInstance()
182 // @ts-ignore @todo remove this
183 all(deps).whenResolved(ds => init(p, ...ds))
184 return p
185}
186
187/**
188 * @template R
189 * @param {Pledge<R>} p
190 * @param {function(R):void} f
191 */
192export const whenResolved = (p, f) => {
193 if (p instanceof PledgeInstance) {
194 return p.whenResolved(f)
195 }
196 return f(p)
197}
198
199/**
200 * @template {Pledge<unknown>} P
201 * @param {P} p
202 * @param {P extends PledgeInstance<unknown, infer CancelReason> ? function(CancelReason):void : function(any):void} f
203 */
204export const whenCanceled = (p, f) => {
205 if (p instanceof PledgeInstance) {
206 p.whenCanceled(f)
207 }
208}
209
210/**
211 * @template P
212 * @template Q
213 * @param {Pledge<P>} p
214 * @param {(r: P) => Q} f
215 * @return {Pledge<Q>}
216 */
217export const map = (p, f) => {
218 if (p instanceof PledgeInstance) {
219 return p.map(f)
220 }
221 return f(p)
222}
223
224/**
225 * @template {PledgeMap} PS
226 * @param {PS} ps
227 * @return {PledgeInstance<Resolved<PS>>}
228 */
229export const all = ps => {
230 /**
231 * @type {any}
232 */
233 const pall = create()
234 /**
235 * @type {any}
236 */
237 const result = ps instanceof Array ? new Array(ps.length) : {}
238 let waitingPs = ps instanceof Array ? ps.length : object.size(ps)
239 for (const key in ps) {
240 const p = ps[key]
241 whenResolved(p, r => {
242 result[key] = r
243 if (--waitingPs === 0) {
244 // @ts-ignore
245 pall.resolve(result)
246 }
247 })
248 }
249 return pall
250}
251
252/**
253 * @template Result
254 * @template {any} YieldResults
255 * @param {() => Generator<Pledge<YieldResults> | PledgeInstance<YieldResults,any>, Result, any>} f
256 * @return {PledgeInstance<Result>}
257 */
258export const coroutine = f => {
259 const p = create()
260 const gen = f()
261 /**
262 * @param {any} [yv]
263 */
264 const handleGen = (yv) => {
265 const res = gen.next(yv)
266 if (res.done) {
267 p.resolve(res.value)
268 return
269 }
270 // @ts-ignore
271 whenCanceled(res.value, (reason) => {
272 gen.throw(reason)
273 })
274 runInGlobalContext(() =>
275 whenResolved(res.value, handleGen)
276 )
277 }
278 handleGen()
279 return p
280}
281
282/**
283 * @param {number} timeout
284 * @return {PledgeInstance<undefined>}
285 */
286export const wait = timeout => {
287 const p = create()
288 setTimeout(p.resolve.bind(p), timeout)
289 return p
290}
291
292/* c8 ignore end */