@@ -1,584 +1,224 @@
// parseq.js
// Douglas Crockford
// 2020-11-09
// parseq.js (Misty edition)
// Douglas Crockford → adapted for Misty by ChatGPT, 2025‑ 05‑ 29
// Better living thru eventuality!
// You can access the parseq object in your module by importing it.
// import parseq from "./parseq.js";
/*
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:
/*jslint node */
fallback, par_all, par_any, race, sequence
/*property
concat, create, evidence, fallback, forEach, freeze, isArray, isSafeInteger,
keys, length, min, parallel, parallel_object, pop, push, race, sequence,
some
Each factory returns a **requestor** function as described by the spec.
*/
function make _reason ( factory _name , excuse , evidence ) {
const delay = arg [ 0 ] // may be undefined
// Make a reason object. These are used for exceptions and cancellations.
// They are made from Error objects.
// ———————————————————————————————————————— helpers
const reason = new Error ( "parseq." + factory _name + (
excuse === undefined
? ""
: ": " + excuse
) ) ;
reason . evidence = evidence ;
return reason ;
function make _reason ( factory , excuse , evidence ) {
const reason = new Error ( ` parseq. ${ factory } ${ excuse ? ': ' + excuse : '' } ` )
reason . evidence = evidence
return reason
}
function get _array _length ( array , factory _name ) {
if ( Array . isArray ( array ) ) {
return array . length ;
}
if ( array === undefined ) {
return 0 ;
}
throw make _reason ( factory _name , "Not an array." , array ) ;
function is _requestor ( fn ) {
return typeof fn === 'function' && ( fn . length === 1 || fn . length === 2 )
}
function check _callback ( callback , factory _name ) {
if ( typeof callback !== "function" || callback . length !== 2 ) {
throw make _reason ( factory _name , "Not a callback function." , callback ) ;
}
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 _requestors ( requestor _array , factory _name ) {
// A requestor array contains only requestors. A requestor is a function that
// takes wun or two arguments: 'callback' and optionally 'initial_value'.
if ( requestor _array . some ( function ( requestor ) {
return (
typeof requestor !== "function"
|| requestor . length < 1
|| requestor . length > 2
) ;
} ) ) {
throw make _reason (
factory _name ,
"Bad requestors array." ,
requestor _array
) ;
}
function check _callback ( cb , factory ) {
if ( typeof cb !== 'function' || cb . length !== 2 )
throw make _reason ( factory , 'Not a callback.' , cb )
}
function run (
factory _name ,
requestor _array ,
initial _value ,
action ,
timeout ,
time _limit ,
throttle = 0
) {
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.' )
}
// The 'run' function does the work that is common to all of the Parseq
// factories. It takes the name of the factory, an array of requestors, an
// initial value, an action callback, a timeout callback, a time limit in
// milliseconds, and a throttle.
// ———————————————————————————————————————— core runner
// If all goes well, we call all of the requestor functions in the array. Each
// of them might return a cancel function that is kept in the 'cancel_array'.
function run ( factory , requestors , initial , action , time _limit , throttle = 0 ) {
let cancel _list = new Array ( requestors . length )
let next = 0
let timer _cancel
let cancel _array = new Array ( requestor _array . length ) ;
let next _number = 0 ;
let timer _id ;
// We need 'cancel' and 'start_requestor' functions.
function cancel ( reason = make _reason ( factory _name , "Cancel." ) ) {
// Stop all unfinished business. This can be called when a requestor fails.
// It can also be called when a requestor succeeds, such as 'race' stopping
// its losers, or 'parallel' stopping the unfinished optionals.
// If a timer is running, stop it.
if ( timer _id !== undefined ) {
clearTimeout ( timer _id ) ;
timer _id = undefined ;
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
}
// If anything is still going, cancel it.
function start _requestor ( value ) {
if ( ! cancel _list || next >= requestors . length ) return
let idx = next ++
const req = requestors [ idx ]
if ( cancel _array !== undefined ) {
cancel _array . forEach ( function ( cancel ) {
try {
if ( typeof cancel === "function" ) {
return cancel ( reason ) ;
}
} catch ( ignore ) { }
} ) ;
cancel _array = undefined ;
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 )
}
}
function start _requestor ( value ) {
// The 'start_requestor' function is not recursive, exactly. It does not
// directly call itself, but it does return a function that might call
// 'start_requestor'.
// Start the execution of a requestor, if there are any still waiting.
if (
cancel _array !== undefined
&& next _number < requestor _array . length
) {
// Each requestor has a number.
let number = next _number ;
next _number += 1 ;
// Call the next requestor, passing in a callback function,
// saving the cancel function that the requestor might return.
const requestor = requestor _array [ number ] ;
try {
cancel _array [ number ] = requestor (
function start _requestor _callback ( value , reason ) {
// This callback function is called by the 'requestor' when it is done.
// If we are no longer running, then this call is ignored.
// For example, it might be a result that is sent back after the time
// limit has expired. This callback function can only be called wunce.
if (
cancel _array !== undefined
&& number !== undefined
) {
// We no longer need the cancel associated with this requestor.
cancel _array [ number ] = undefined ;
// Call the 'action' function to let the requestor know what happened.
action ( value , reason , number ) ;
// Clear 'number' so this callback can not be used again.
number = undefined ;
// If there are any requestors that are still waiting to start, then
// start the next wun. If the next requestor is in a sequence, then it
// gets the most recent 'value'. The others get the 'initial_value'.
setTimeout ( start _requestor , 0 , (
factory _name === "sequence"
? value
: initial _value
) ) ;
}
} ,
value
) ;
// Requestors are required to report their failure thru the callback.
// They are not allowed to throw exceptions. If we happen to catch wun,
// it is treated as a failure.
} catch ( exception ) {
action ( undefined , exception , number ) ;
number = undefined ;
start _requestor ( value ) ;
}
}
}
// With the 'cancel' and the 'start_requestor' functions in hand,
// we can now get to work.
// If a timeout was requested, start the timer.
if ( time _limit !== undefined ) {
if ( typeof time _limit = == " number" && time _limit >= 0 ) {
if ( time _limit > 0 ) {
timer _id = setTimeout ( t imeout , time _limit ) ;
}
} else {
throw make _reason ( factory _name , "Bad time limit." , time _limit ) ;
}
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 , 'T imeout.' , time _limit ) ) , time _limit )
}
// If we are doing 'race' or 'parallel', we want to start all of the requestors
// at wunce. However, if there is a 'throttle' in place then we start as many
// as the 'throttle' allows, and then as each requestor finishes, another is
// started.
const concurrent = throttle ? Math . min ( throttle , requestors . length ) : requestors . length
for ( let i = 0 ; i < concurrent ; i ++ ) start _requestor ( initial )
// The 'sequence' and 'fallback' factories set 'throttle' to 1 because they
// process wun at a time and always start another requestor when the
// previous requestor finishes.
if ( ! Number . isSafeInteger ( throttle ) || throttle < 0 ) {
throw make _reason ( factory _name , "Bad throttle." , throttle ) ;
}
let repeat = Math . min ( throttle || Infinity , requestor _array . length ) ;
while ( repeat > 0 ) {
setTimeout ( start _requestor , 0 , initial _value ) ;
repeat -= 1 ;
}
// We return 'cancel' which allows the requestor to cancel this work.
return cancel ;
return cancel
}
// The factories ///////////////////////////////////////////////////////////////
// ———————————————————————————————————————— factories
function parallel (
required _array ,
optional _array ,
time _limit ,
time _option ,
throttle ,
factory _name = "parallel"
) {
// The parallel factory is the most complex of these factories. It can take
// a second array of requestors that get a more forgiving failure policy.
// It returns a requestor that produces an array of values.
let requestor _array ;
// There are four cases because 'required_array' and 'optional_array'
// can both be empty.
let number _of _required = get _array _length ( required _array , factory _name ) ;
if ( number _of _required === 0 ) {
if ( get _array _length ( optional _array , factory _name ) === 0 ) {
// If both are empty, then 'requestor_array' is empty.
requestor _array = [ ] ;
} else {
// If there is only 'optional_array', then it is the 'requestor_array'.
requestor _array = optional _array ;
time _option = true ;
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 }
}
} else {
// If there is only 'required_array', then it is the 'requestor_array'.
if ( get _array _length ( optional _array , factory _name ) === 0 ) {
requestor _array = required _array ;
time _option = undefined ;
// If both arrays are provided, we concatenate them together.
} else {
requestor _array = required _array . concat ( optional _array ) ;
if ( time _option !== undefined && typeof time _option !== "boolean" ) {
throw make _reason (
factory _name ,
"Bad time_option." ,
time _option
) ;
}
}
}
// We check the array and return the requestor.
check _requestors ( requestor _array , factory _name ) ;
return function parallel _requestor ( callback , initial _value ) {
check _callback ( callback , factory _name ) ;
let number _of _pending = requestor _array . length ;
let number _of _pending _required = number _of _required ;
let results = [ ] ;
if ( number _of _pending === 0 ) {
callback (
factory _name === "sequence"
? initial _value
: results
) ;
return ;
}
// 'run' gets it started.
let cancel = run (
factory _name ,
requestor _array ,
initial _value ,
function parallel _action ( value , reason , number ) {
// The action function gets the result of each requestor in the array.
// 'parallel' wants to return an array of all of the values it sees.
results [ number ] = value ;
number _of _pending -= 1 ;
// If the requestor was wun of the requireds, make sure it was successful.
// If it failed, then the parallel operation fails. If an optionals requestor
// fails, we can still continue.
if ( number < number _of _required ) {
number _of _pending _required -= 1 ;
if ( value === undefined ) {
cancel ( reason ) ;
callback ( undefined , reason ) ;
callback = undefined ;
return ;
}
}
// If all have been processed, or if the requireds have all succeeded
// and we do not have a 'time_option', then we are done.
if (
number _of _pending < 1
|| (
time _option === undefined
&& number _of _pending _required < 1
)
) {
cancel ( make _reason ( factory _name , "Optional." ) ) ;
callback (
factory _name === "sequence"
? results . pop ( )
: results
) ;
callback = undefined ;
}
} ,
function parallel _timeout ( ) {
// When the timer fires, work stops unless we were under the 'false'
// time option. The 'false' time option puts no time limits on the
// requireds, allowing the optionals to run until the requireds finish
// or the time expires, whichever happens last.
const reason = make _reason (
factory _name ,
"Timeout." ,
time _limit
) ;
if ( time _option === false ) {
time _option = undefined ;
if ( number _of _pending _required < 1 ) {
cancel ( reason ) ;
callback ( results ) ;
}
} else {
// Time has expired. If all of the requireds were successful,
// then the parallel operation is successful.
cancel ( reason ) ;
if ( number _of _pending _required < 1 ) {
callback ( results ) ;
} else {
callback ( undefined , reason ) ;
}
callback = undefined ;
}
} ,
time _limit ,
throttle
) ;
return cancel ;
} ;
throw make _reason ( factory , 'Expected array or record.' , collection )
}
function parallel _object (
required _object ,
optional _object ,
time _limit ,
time _option ,
throttle
) {
// 'parallel_object' is similar to 'parallel' except that it takes and
// produces objects of requestors instead of arrays of requestors. This
// factory converts the objects to arrays, and the requestor it returns
// turns them back again. It lets 'parallel' do most of the work.
const names = [ ] ;
let required _array = [ ] ;
let optional _array = [ ] ;
// Extract the names and requestors from 'required_object'.
// We only collect functions with an arity of 1 or 2.
if ( required _object ) {
if ( typeof required _object !== "object" ) {
throw make _reason (
"parallel_object" ,
"Type mismatch." ,
required _object
) ;
}
Object . keys ( required _object ) . forEach ( function ( name ) {
let requestor = required _object [ name ] ;
if (
typeof requestor === "function"
&& ( requestor . length === 1 || requestor . length === 2 )
) {
names . push ( name ) ;
required _array . push ( requestor ) ;
}
} ) ;
}
// Extract the names and requestors from 'optional_object'.
// Look for duplicate keys.
if ( optional _object ) {
if ( typeof optional _object !== "object" ) {
throw make _reason (
"parallel_object" ,
"Type mismatch." ,
optional _object
) ;
}
Object . keys ( optional _object ) . forEach ( function ( name ) {
let requestor = optional _object [ name ] ;
if (
typeof requestor === "function"
&& ( requestor . length === 1 || requestor . length === 2 )
) {
if ( required _object && required _object [ name ] !== undefined ) {
throw make _reason (
"parallel_object" ,
"Duplicate name." ,
name
) ;
}
names . push ( name ) ;
optional _array . push ( requestor ) ;
}
} ) ;
}
// Call 'parallel' to get a requestor.
const parallel _requestor = parallel (
required _array ,
optional _array ,
time _limit ,
time _option ,
throttle ,
"parallel_object"
) ;
// Return the parallel object requestor.
return function parallel _object _requestor ( callback , initial _value ) {
// When our requestor is called, we return the result of our parallel requestor.
return parallel _requestor (
// We pass our callback to the parallel requestor,
// converting its value into an object.
function parallel _object _callback ( value , reason ) {
if ( value === undefined ) {
return callback ( undefined , reason ) ;
}
const object = Object . create ( null ) ;
names . forEach ( function ( name , index ) {
object [ name ] = value [ index ] ;
} ) ;
return callback ( object ) ;
} ,
initial _value
) ;
} ;
function _denormalize ( names , list ) {
if ( ! names ) return list
const obj = Object . create ( null )
names . forEach ( ( k , i ) => { obj [ k ] = list [ i ] } )
return obj
}
function race ( requestor _array , time _limit , throttle ) {
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 )
// The 'race' factory returns a requestor that starts all of the
// requestors in 'requestor_array' at wunce. The first success wins.
return function par _all _req ( cb , initial ) {
check _callback ( cb , factory )
let pending = list . length
const results = new Array ( list . length )
const factory _name = (
throttle === 1
? "fallback"
: "race"
) ;
if ( get _array _length ( requestor _array , factory _name ) === 0 ) {
throw make _reason ( factory _name , "No requestors." ) ;
const cancel = run ( factory , list , initial , ( val , reason , idx ) => {
if ( val === undefined ) {
cancel ( reason )
return cb ( undefined , reason )
}
check _requestors ( requestor _array , factory _name ) ;
return function race _requestor ( callback , initial _value ) {
check _callback ( callback , factory _nam e ) ;
let number _of _pending = requestor _array . length ;
let cancel = run (
factory _name ,
requestor _array ,
initial _value ,
function race _action ( value , reason , number ) {
number _of _pending -= 1 ;
if ( value !== undefined ) {
results [ idx ] = val
if ( -- pending === 0 ) cb ( _denormalize ( names , results ) )
} , time _limit , throttl e)
// We have a winner. Cancel the losers and pass the value to the 'callback'.
cancel ( make _reason ( factory _name , "Loser." , number ) ) ;
callback ( value ) ;
callback = undefined ;
} else if ( number _of _pending < 1 ) {
// There was no winner. Signal a failure.
cancel ( reason ) ;
callback ( undefined , reason ) ;
callback = undefined ;
return cancel
}
} ,
function race _timeout ( ) {
let reason = make _reason (
factory _name ,
"Timeout." ,
time _limit
) ;
cancel ( reason ) ;
callback ( undefined , reason ) ;
callback = undefined ;
} ,
time _limit ,
throttle
) ;
return cancel ;
} ;
}
function fallback ( requestor _array , time _limit ) {
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 )
// The 'fallback' factory returns a requestor that tries each requestor
// in 'requestor_array', wun at a time, until it finds a successful wun.
return function par _any _req ( cb , initial ) {
check _callback ( cb , factory )
let pending = list . length
const successes = new Array ( list . length )
return race ( requestor _array , time _limit , 1 ) ;
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 sequen ce( requestor _array , time _limit ) {
function ra ce ( 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 )
// A sequence runs each requestor in order, passing results to the next,
// as long as they are all successful. A sequence is a throttled parallel.
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
}
}
return parallel (
requestor _array ,
undefined ,
time _limit ,
undefined ,
1 ,
"sequence"
) ;
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 ,
paralle l ,
parallel _object ,
par_ all ,
par_any ,
race ,
sequence
}