Files
cell/scripts/modules/parseq.js

225 lines
7.0 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// parseq.js (Misty edition)
// Douglas Crockford → adapted for Misty by ChatGPT, 20250529
// Better living thru eventuality!
/*
 The original parseq.js relied on the browser's setTimeout and ran in
 milliseconds. In Misty we may be given an optional @.delay capability
 (arguments[0]) and time limits are expressed in **seconds**. This rewrite
 removes the setTimeout dependency, uses the @.delay capability when it is
 present, and provides the factories described in the Misty specification:
    fallback, par_all, par_any, race, sequence
 Each factory returns a **requestor** function as described by the spec.
*/
const delay = arg[0] // may be undefined
// ———————————————————————————————————————— helpers
function make_reason (factory, excuse, evidence) {
const reason = new Error(`parseq.${factory}${excuse ? ': ' + excuse : ''}`)
reason.evidence = evidence
return reason
}
function is_requestor (fn) {
return typeof fn === 'function' && (fn.length === 1 || fn.length === 2)
}
function check_requestors (list, factory) {
if (!Array.isArray(list) || list.some(r => !is_requestor(r)))
throw make_reason(factory, 'Bad requestor list.', list)
}
function check_callback (cb, factory) {
if (typeof cb !== 'function' || cb.length !== 2)
throw make_reason(factory, 'Not a callback.', cb)
}
function schedule (fn, seconds) {
if (seconds === undefined || seconds <= 0) return fn()
if (typeof delay === 'function') return delay(fn, seconds)
throw make_reason('schedule', '@.delay capability required for timeouts.')
}
// ———————————————————————————————————————— core runner
function run (factory, requestors, initial, action, time_limit, throttle = 0) {
let cancel_list = new Array(requestors.length)
let next = 0
let timer_cancel
function cancel (reason = make_reason(factory, 'Cancel.')) {
if (timer_cancel) timer_cancel(), timer_cancel = undefined
if (!cancel_list) return
cancel_list.forEach(c => { try { if (typeof c === 'function') c(reason) } catch (_) {} })
cancel_list = undefined
}
function start_requestor (value) {
if (!cancel_list || next >= requestors.length) return
let idx = next++
const req = requestors[idx]
try {
cancel_list[idx] = req(function req_cb (val, reason) {
if (!cancel_list || idx === undefined) return
cancel_list[idx] = undefined
action(val, reason, idx)
idx = undefined
if (factory === 'sequence') start_requestor(val)
else if (throttle) start_requestor(initial)
}, value)
} catch (ex) {
action(undefined, ex, idx)
idx = undefined
if (factory === 'sequence') start_requestor(value)
else if (throttle) start_requestor(initial)
}
}
if (time_limit !== undefined) {
if (typeof time_limit !== 'number' || time_limit < 0)
throw make_reason(factory, 'Bad time limit.', time_limit)
if (time_limit > 0) timer_cancel = schedule(() => cancel(make_reason(factory, 'Timeout.', time_limit)), time_limit)
}
const concurrent = throttle ? Math.min(throttle, requestors.length) : requestors.length
for (let i = 0; i < concurrent; i++) start_requestor(initial)
return cancel
}
// ———————————————————————————————————————— factories
function _normalize (collection, factory) {
if (Array.isArray(collection)) return { names: null, list: collection }
if (collection && typeof collection === 'object') {
const names = Object.keys(collection)
const list = names.map(k => collection[k]).filter(is_requestor)
return { names, list }
}
throw make_reason(factory, 'Expected array or record.', collection)
}
function _denormalize (names, list) {
if (!names) return list
const obj = Object.create(null)
names.forEach((k, i) => { obj[k] = list[i] })
return obj
}
function par_all (collection, time_limit, throttle) {
const factory = 'par_all'
const { names, list } = _normalize(collection, factory)
if (list.length === 0) return (cb, v) => cb(names ? {} : [])
check_requestors(list, factory)
return function par_all_req (cb, initial) {
check_callback(cb, factory)
let pending = list.length
const results = new Array(list.length)
const cancel = run(factory, list, initial, (val, reason, idx) => {
if (val === undefined) {
cancel(reason)
return cb(undefined, reason)
}
results[idx] = val
if (--pending === 0) cb(_denormalize(names, results))
}, time_limit, throttle)
return cancel
}
}
function par_any (collection, time_limit, throttle) {
const factory = 'par_any'
const { names, list } = _normalize(collection, factory)
if (list.length === 0) return (cb, v) => cb(names ? {} : [])
check_requestors(list, factory)
return function par_any_req (cb, initial) {
check_callback(cb, factory)
let pending = list.length
const successes = new Array(list.length)
const cancel = run(factory, list, initial, (val, reason, idx) => {
pending--
if (val !== undefined) successes[idx] = val
if (successes.some(v => v !== undefined)) {
if (!pending) cancel(make_reason(factory, 'Finished.'))
return cb(_denormalize(names, successes.filter(v => v !== undefined)))
}
if (!pending) cb(undefined, make_reason(factory, 'No successes.'))
}, time_limit, throttle)
return cancel
}
}
function race (list, time_limit, throttle) {
const factory = throttle === 1 ? 'fallback' : 'race'
if (!Array.isArray(list) || list.length === 0)
throw make_reason(factory, 'No requestors.')
check_requestors(list, factory)
return function race_req (cb, initial) {
check_callback(cb, factory)
let done = false
const cancel = run(factory, list, initial, (val, reason, idx) => {
if (done) return
if (val !== undefined) {
done = true
cancel(make_reason(factory, 'Loser.', idx))
cb(val)
} else if (--list.length === 0) {
done = true
cancel(reason)
cb(undefined, reason)
}
}, time_limit, throttle)
return cancel
}
}
function fallback (list, time_limit) {
return race(list, time_limit, 1)
}
function sequence (list, time_limit) {
const factory = 'sequence'
if (!Array.isArray(list)) throw make_reason(factory, 'Not an array.', list)
check_requestors(list, factory)
if (list.length === 0) return (cb, v) => cb(v)
return function sequence_req (cb, initial) {
check_callback(cb, factory)
let idx = 0
function next (value) {
if (idx >= list.length) return cb(value)
try {
list[idx++](function seq_cb (val, reason) {
if (val === undefined) return cb(undefined, reason)
next(val)
}, value)
} catch (ex) {
cb(undefined, ex)
}
}
next(initial)
}
}
return {
fallback,
par_all,
par_any,
race,
sequence
}