decurse
Version:
An abstraction over continuation-passing and trampolining to write recursive functions that don't exceed the maximum call stack size.
62 lines (61 loc) • 1.53 kB
JavaScript
class OffStack {
#resolved = !1
#value = void 0
#dependents = []
#decurse
constructor(decurse, executor) {
this.#decurse = decurse
decurse.pending.push(() => executor(value => this.#resolve(value)))
decurse.autoPump && pumpAll(decurse)
}
#resolve(value) {
if (!this.#resolved) {
if (value instanceof OffStack) {
if (!value.#resolved) {
value.#dependents.push(value => this.#resolve(value))
return
}
value = value.#value
}
this.#resolved = !0
this.#value = value
for (const dependent of this.#dependents) this.#decurse.pending.push(() => dependent(this.#value))
this.#decurse.autoPump && pumpAll(this.#decurse)
}
}
then(callback) {
return new OffStack(this.#decurse, resolve => {
this.#resolved ?
this.#decurse.pending.push(() => resolve(callback(this.#value)))
: this.#dependents.push(value => resolve(callback(value)))
})
}
}
function pump(decurse) {
if (!decurse.active) {
decurse.active = !0
try {
decurse.pending.length && decurse.pending.pop()()
} finally {
decurse.active = !1
}
}
}
function pumpAll(decurse) {
if (!decurse.active) {
decurse.active = !0
try {
for (; decurse.pending.length; ) decurse.pending.pop()()
} finally {
decurse.active = !1
}
}
}
function makeDecurse({ autoPump = !0 } = {}) {
const decurse = callback => new OffStack(decurse, resolve => resolve(callback()))
decurse.pending = []
decurse.active = !1
decurse.autoPump = autoPump
return decurse
}
export { OffStack, makeDecurse, pump, pumpAll }