types.js
Library of JavaScript type extensions, types and utilities.
-
types.js
-
Installation
-
Basic usage
-
Object
-
Object.deepKeys(..)
-
Object.copy(..) (EXPERIMENTAL)
-
Object.flatCopy(..)
-
Object.match(..)
-
Object.matchPartial(..)
-
.run(..)
-
Object.sort(..)
-
Function
-
func.AsyncFunction
-
Array
-
.first(..) / .last(..)
-
.rol(..)
-
.compact()
-
.len
-
.unique() / .tailUnique()
-
.trim() / .trimStart() / .trimEnd()
-
.cmp(..)
-
.setCmp(..)
-
.sortAs(..)
-
.inplaceSortAs(..)
-
.toKeys(..)
-
.toMap(..)
-
Array.zip(..) / .zip(..)
-
Array.iter(..) / .iter()
-
.between(..)
-
Abortable Array iteration
-
array.STOP / array.STOP(..)
-
.smap(..) / .sfilter(..) / .sreduce(..) / .sforEach(..)
-
Large Array iteration (chunked)
-
array.STOP / array.STOP(..)
-
.CHUNK_SIZE
-
.mapChunks(..) / .filterChunks(..) / .reduceChunks(..)
-
Map
-
.replaceKey(..)
-
.sort(..)
-
Set
-
.unite(..)
-
.intersect(..)
-
.subtract(..)
-
.splice(..)
-
.replace(..)
-
.replaceAt(..)
-
.sort(..)
-
.filter(..) / .map(..) / .forEach(..) / .reduce(..) / .reduceRight(..)
-
Date
-
Date.timeStamp(..)
-
Date.fromTimeStamp(..)
-
Date.str2ms(..)
-
.toShortDate(..)
-
.getTimeStamp(..)
-
.setTimeStamp(..)
-
String
-
.capitalize()
-
.indent(..)
-
RegExp
-
RegExp.quoteRegExp(..)
-
Promise
-
Interactive promises
-
Promise.interactive(..)
-
.send(..)
-
.then(..)
-
Cooperative promises
-
Promise.cooperative()
-
.set(..)
-
.isSet
-
.then(..)
-
Promise iteration
-
Promise.iter(..) / promise.IterablePromise(..)
-
.iter()
-
.iter()
-
Promise.seqiter(..) / promise.IterableSequentialPromise(..)
-
.seqiter() / .seqiter()
-
Promise.seqstartiter(..) / promise.IterableSequentialStartPromise(..)
-
.seqstartiter() / .seqstartiter()
-
.map(..) / .filter(..) / .reduce(..)
-
.between(..)
-
.flat(..)
-
.reverse()
-
.concat(..)
-
.push(..) / .unshift(..)
-
.at(..) / .first() / .last()
-
.join(..)
-
.some(..) / .find(..)
-
Array proxy methods returning
-
Array proxy methods returning a
-
.then(..) / .catch(..) / .finally(..)
-
.iterthen(..)
-
.isSync(..)
-
.sync(..)
-
promise.IterablePromise.STOP / promise.IterablePromise.STOP(..)
-
Advanced handler
-
Stopping the iteration
-
Promise proxies
-
.as(..)
-
.(..)
-
Sync/async promise
-
Promise.sync(..) / promise.SyncPromise(..)
-
.sync(..)
-
.value / .error
-
" class="text-primary hover:underline" target="_blank" rel="noopener noreferrer">Promise.sync.all(..) / Promise.sync.allSettled(..) / Promise.sync.any(..) / Promise.sync.race(..)
-
Promise utilities
-
Promise.awaitOrRun(..)
-
Generator extensions and utilities
-
The basics
-
generator.Generator
-
generator.iter(..)
-
generator.STOP
-
Generator instance iteration
-
.iter(..)
-
.map(..) / .filter(..)
-
.reduce(..) / .greduce(..)
-
.forEach(..) (EXPERIMENTAL)
-
.between(..)
-
.slice(..)
-
.at(..) / .gat(..)
-
.flat(..)
-
.shift() / .pop() / .gshift() / .gpop()
-
.unshift(..) / .push(..)
-
.join(..)
-
.unwind(..)
-
.then(..) / .catch(..) / .finally(..)
-
.toArray()
-
Treating iterators the same as generators
-
Generator constructor iteration
-
.iter(..)
-
.at(..) / .gat(..)
-
.shift() / .pop() / .gshift() / .gpop()
-
.unshift(..) / .push(..)
-
.slice(..)
-
.map(..) / .filter(..) / .reduce(..) / .flat()
-
.between(..)
-
.toArray()
-
.join(..)
-
.unwind(..)
-
Generator combinators
-
.chain(..) / .chain(..)
-
.concat(..) / .concat(..)
-
Generator library
-
generator.range(..)
-
generator.repeat(..)
-
generator.produce(..)
-
Generator helpers
-
generator.stoppable(..)
-
Async generator extensions
-
generator.AsyncGenerator
-
.unwind(..)
-
.then(..) / .catch(..) / .finally(..)
-
.iter(..)
-
.map(..) / .filter(..) / .reduce(..)
-
.chain(..)
-
.flat(..)
-
.concat(..)
-
.push(..) / .unshift(..)
-
Containers
-
containers.UniqueKeyMap() (Map)
-
.set(..)
-
.reset(..)
-
.rename(..)
-
.orderedRename(..)
-
.unorderedRename(..)
-
.keysOf(..)
-
.originalKey(..)
-
.uniqueKey(..)
-
.__key_pattern__
-
.__unordered_rename__
-
Event
-
event.Eventfull(..)
-
event.Event(..)
-
event.PureEvent(..)
-
event.TRIGGER
-
event.EventHandlerMixin
-
.on(..)
-
.one(..)
-
.off(..)
-
.trigger(..)
-
event.EventDocMixin
-
.eventfull
-
.events
-
event.EventMixin
-
Runner
-
Micro task queue
-
runner.STOP
-
runner.SKIP
-
Queue(..) / Queue.runTasks(..)
-
Queue.handle(..)
-
.state
-
.start(..)
-
.stop(..)
-
.runTask(..)
-
.tasksAdded(..) (event)
-
.taskStarting(..) (event)
-
.taskFailed(..) (event)
-
.taskCompleted(..) (event)
-
.queueEmpty(..) (event)
-
.prioritize(..)
-
.delay(..)
-
.add(..)
-
.clear(..)
-
FinalizableQueue(..) / FinalizableQueue.runTasks(..) (Queue)
-
.done(..) (event/method)
-
.abort(..) (event/method)
-
.promise(..)
-
.then(..)
-
.catch(..)
-
Large task management
-
runner.TaskManager(..)
-
.Task(..)
-
.sync_start
-
.record_times
-
.titled(..)
-
.send(..)
-
.stop(..)
-
.done(..) (event)
-
.error(..) (event)
-
.tasksDone(..) (event)
-
runner.TaskTicket(..)
-
runner.TaskMixin(..)
-
License
Installation
`
shell
$ npm install -s 'ig-types'
`
Basic usage
To extend everything:
`
javascript
require('ig-types')
`
To have access to additional library types and utilities:
`
javascript
var types = require('ig-types')
`
types.js
is organized so as to be able to import/extend only specific
sub-modules mostly independently so...
In case there is a need to only extend a specific constructor just import
the module dealing with that constructor (Array
in this case):
`
javascript
// require ig-types/
...
require('ig-types/Array')
`
Note that type patching modules are _mostly_ independent.
And to import specific library modules only:
`javascript
var containers = require('ig-types/containers')
`
Object
`javascript
require('ig-types/Object')
`
Note that this module imports from
object.js and
object-run.js,
see those modules for more details.
$3
Get list of keys from all objects in the prototype chain.
`bnf
Object.deepKeys()
->
`
This is different from Object.keys(..) which only gets _own_ keys from the
current object.
Example:
`javascript
var a = { x: 123 }
var b = Object.create(a)
b.y = 321
// get own keys of b...
Object.keys(b) // -> ['y']
// get all keys accessible from b...
Object.deepKeys(b) // -> ['x', 'y']
`
For more details see:
https://github.com/flynx/object.js#deepkeys
$3
Create a copy of
`bnf
Object.copy()
->
Object.copy(, )
->
`
This will:
- create a blank
- link to the same prototype chain
- assign all _own_ keys from to
This is similar to Object.clone(..) but instead of creating a new descendant of
the input object with no data this will instead create a new sibling with a copy
of the instance data.
if given is called to create the instance to be populated,
otherwise Object.create() is used.
Note that .assign(..) is used to copy data, thus properties will be copied as values, to copy instance properties use object.js's
.mixinFlat(..).
Note that this will make no attempt to clone object type, a
should be passed manually if any instance type other that Object is required.
$3
Copy all attributes from the prototype chain of into .
`bnf
Object.flatCopy()
->
Object.flatCopy(, )
->
`
This is different to .copy(..) in that if
no is given will _not_ be linked into the
prototype chain of , if this behavior is desired use o => Object.create(o)
as the .
$3
Attribute/value match two objects (non-recursive).
`bnf
Object.match(, )
->
`
Objects A and B match iff:
- A and B are _identical_, i.e. A === B
or
- typeof A == typeof B _and_,
- A and B have the same number of attributes _and_,
- attribute names match _and_,
- attribute values are _identical_.
And for a less strict match:
`bnf
Object.match(, , true)
->
`
Like the default case but uses _equality_ instead of _identity_ to match values.
For more details see:
https://github.com/flynx/object.js#match
$3
`bnf
Object.matchPartial(, )
->
Object.matchPartial(, , true)
->
`
Like .match(..) but will check for a _partial_ match, i.e. when is a non-strict subset of .
For more details see:
https://github.com/flynx/object.js#matchpartial
$3
`bnf
.run()
->
->
`
Run a function in the context of returning either
itself (if returning undefined) or the result.
Note that this is accessible from all JavaScript non-primitive objects,
i.e. everything that inherits from Object.
Example:
`javascript
var L = [1, 2, 3]
.map(function(e){
return e * 2 })
// see if the first element is 1 and prepend 1 if it is not...
.run(function(){
if(this[0] != 1){
this.unshift(1) } })
console.log(L) // -> [1, 2, 6, 8]
`
.run(..) is also available standalone via:
`shell
$ npm install -s object-run
`
For more details see:
https://github.com/flynx/object-run.js
$3
Sort attributes (similar to Array's .sort(..))
`bnf
Object.sort()
->
`
Sort attributes via function.
`
Object.sort(, )
->
`
Sort attributes to the same order of .
`bnf
Object.sort(, )
->
`
Note that this rewrites all the keys of thus for very large
sets of keys/attributes this may be quite expensive.
Note that some keys of Object may misbehave in JavaScript, currently keys
that are string values of numbers are sorted automatically by _number value_
and are not affected by .sort(..), this affects both _Chrome_ and _Firefox_.
Example:
`javascript
var o = {x: 0, a: 1, '100':2, '0':3, ' 27 ':4, b:5}
// notice that the order is already different to the order of attributes above...
Object.keys(o)
// -> ['0', '100', 'x', 'a', ' 27 ', 'b']
// '0' and '100' are not affected by .sort(..) while ' 27 ' is...
Object.keys(Object.sort(o, ['x', 'a', '100']))
// -> [ '0', '100', 'x', 'a', ' 27 ', 'b' ]
`
This is similar to .sort(..) and .sort(..).
Function
`javascript
var func = require('ig-types/Function')
`
$3
The async function constructor.
This enables us to test if an object is an instance of an async function.
`javascript
var a = async function(){
// ...
}
a instanceof func.AsyncFunction // -> true
`
This is hidden by JavaScript by default.
Array
`javascript
require('ig-types/Array')
`
or
`javascript
var array = require('ig-types/Array')
`
$3
Get the first/last items of .
`bnf
.first()
-> -
.last()
-> -
`
Set the first/last items of .
`bnf
.first(- )
->
.last(- )
->
`
Note that these do not affect length unless setting items on
an empty .
$3
Roll in-place left.
`bnf
.rol()
.rol(1)
->
.rol(n)
->
`
To roll _right_ pass a negative n to .rol(..).
$3
`bnf
.compact()
->
`
Generate a compact from a sparse , i.e. removing all
the empty slots.
$3
Number of non-empty slots/elements in .
This is similar to:
`javascript
var L = [,,, 1,, 2, 3,,]
// this is the same as L.len...
L.compact().length
`
Note that this is different from .length in that writing to .len has
no effect.
$3
Generate an array with all duplicate elements removed.
`bnf
.unique()
->
.tailUnique()
->
`
The difference between the two versions is in that .unique(..) keeps the
first occurrence of a value while .tailUnique(..) keeps the last.
$3
Copy array removing empty slots from array start, end or both.
`bnf
.trim()
->
.trimStart()
->
.trimEnd()
->
`
This is similar to String's equivalent methods but removing _empty_ slots
instead of spaces.
$3
Compare two arrays.
`bnf
.cmp()
->
`
This will return true if:
- ===
or
- lengths are the same and,
- values on the same positions are equal.
$3
Compare to arrays ignoring element order and count.
`bnf
.setCmp()
->
`
$3
Sort array as a different array.
`bnf
.sortAs()
.sortAs(, 'head')
->
.sortAs(, 'tail')
->
`
Elements not present in retain their relative order and are
placed after the sorted elements if 'head' (i.e. _"sorted at head of
array"_) is passed as second argument (default) and before them if
'tail' (_"sorted at tail"_) is passed.
Example:
`javascript
var L = [1, 2, 3, 4, 5, 6]
var O = [5, 3, 1, 0]
L.sortAs(O) // -> [5, 3, 1, 2, 4, 6]
`
$3
Sort array as a different array keeping positions of unsorted elements.
`bnf
.inplaceSortAs()
->
`
Example:
`javascript
var L = [1, 2, 3, 4, 5, 6]
var O = [5, 3, 1, 0]
L.inplaceSortAs(O) // -> [5, 2, 3, 4, 1, 6]
`
$3
Create an object with array values as keys and index as value.
`bnf
.toKeys()
->
`
Normalize resulting keys:
`bnf
.toKeys()
->
(, )
->
`
If contains the same value multiple times it will be written
to only once with the last occurrences' index.
Since object keys can only be strings array items that are not
strings will be converted to strings. If this is not desired use .toMap(..)
instead.
$3
Create a map with array values as keys and index as value.
`bnf
.toMap()
->
`
Normalize resulting keys:
`bnf
.toMap()
->
(, )
->
`
Note that if contains the same value multiple times it will be used
as key only once and retain the last occurrences' index.
$3
_Zip_ input array items.
`bnf
Array.zip(, , ..)
->
.zip(, , ..)
->
`
Example:
`javascript
var A = [1, 2, 3]
var B = ['a', 'b', 'c', 'd']
Array.zip(A, B) // -> [[1, 'a'], [2, 'b'], [3, 'c'], [, 'd']]
`
Array _sparseness_ is retained -- if one of the arrays has an empty slot, or is
not long enough, the corresponding spot in the result will be empty.
Resulting array length is strictly equal to the longest input array length.
$3
Return an iterator/generator from the current array.
`bnf
.iter()
.iter()
.iter(, )
->
(, )
->
(STOP, ...)
(, ...)
`
This is mostly useful in combination with the Generator extensions and utilities
Note that all stoppable functions/iterators support callback as
the last argument.
$3
`bnf
.between()
->
.between()
->
([, ], , , )
->
`
$3
A an alternative to Array's .map(..) / .filter(..) / .. methods with ability to
stop the iteration process by throwing STOP or STOP().
`javascript
var {STOP} = require('ig-types/Array')
`
This can be used in two ways:
1) throw as-is to simply stop...
`javascript
;[1,2,3,4,5]
.smap(function(e){
// simply abort here and now...
throw STOP })
`
Since we aborted the iteration without passing any arguments to STOP,
.smap(..) will return undefined.
2) throw an instance and return the argument...
`javascript
// this will print "4" -- the value passed to STOP...
console.log([1,2,3,4,5]
.smap(function(e){
if(e > 3){
// NOTE: new is optional here...
// ...StopIteratiom is an object.js constructor.
throw new STOP(e) } }))
`
Note that no partial result is returned unless passed through STOP(..).
#### array.STOP / array.STOP(..)
An _object/constructor_ that if raised (as an exception) while iterating via
a supporting iterator method will abort further execution and correctly exit.
#### .smap(..) / .sfilter(..) / .sreduce(..) / .sforEach(..)
Like Array's .map(..), .filter(..), .reduce(..) and .forEach(..) but
with added support for aborting iteration by throwing STOP or STOP().
These can be passed a callback as an additional last argument
that will be called if STOP/STOP() is returned or thrown.
$3
Iterating over very large Array instances in JavaScript can block execution,
to avoid this types.js implements .map(..)/.filter(..)/.reduce(..)
equivalent methods that iterate the array in chunks and do it asynchronously
giving the runtime a chance to run in between.
In the simplest cases these are almost a drop-in replacements for the equivalent
methods but return a promise.
`javascript
var a = [1,2,3,4,5]
.map(function(e){
return e*2 })
var b
;[1,2,3,4,5]
.mapChunks(function(e){
return e*2 })
.then(function(res){
b = res })
// or with await...
var c = await [1,2,3,4,5]
.mapChunks(function(e){
return e*2 })
`
These support setting the chunk size (default: 50) as the first argument:
`javascript
var c = await [1,2,3,4,5]
.mapChunks(2, function(e){
return e*2 })
`
#### array.STOP / array.STOP(..)
Like for .smap(..) and friends iteration
can be stopped by throwing a array.STOP / array.STOP() and as before
there are two ways to go:
1) throw as-is to simply stop
`javascript
;[1,2,3,4,5]
.mapChunks(function(e){
// simply abort here and now...
throw STOP })
.catch(function(){
console.log('done.') })
`
2) Throw an instance and pass a value to .catch(..)
`javascript
;[1,2,3,4,5]
.mapChunks(function(e){
if(e > 3){
// NOTE: new is optional here...
// ...StopIteratiom is an object.js constructor.
throw new STOP(e) } })
.catch(function(e){
console.log('first value greater than 3:', e) })
`
#### .CHUNK_SIZE
The default iteration chunk size.
Note that the smaller this is the more _responsive_ the code is, especially
in UI applications but there is a small overhead added per chunk.
Default value: 50
#### .mapChunks(..) / .filterChunks(..) / .reduceChunks(..)
The .map(..), .filter(..) and .reduce(..) alternatives respectively:
`bnf
.mapChunks()
.mapChunks(, )
->
(- ,
, )
->
`
`bnf
.filterChunks()
.filterChunks(, )
->
(- ,
, )
->
`
`bnf
.reduceChunks(, )
.mreduceChunks(, , )
->
(, - ,
, )
->
`
All three support chunk handlers in the same way (illustrated on .mapChunks(..)):
`bnf
.mapChunks([, ])
.mapChunks(, [, ])
->
(- ,
, )
->
(, , )
`
The gets the completed chunk of data after it is computed
but before the timeout.
Map
`javascript
require('ig-types/Map')
`
$3
Replace key in map retaining item order
`bnf
.replaceKey(, )
.replaceKey(, , true)
->
`
Replace the key without sorting
`bnf
.replaceKey(, , false)
->
`
Note that when sorting large maps this can get expensive.
$3
Sort keys in-place
`bnf
.sort()
->
.sort()
->
`
In the general case this is similar to
.sort(..)
with the addition of the .sortAs(..)'s ability to sort
as a list
`bnf
.sort()
->
`
This is similar to .sort(..) and Object.sort(..),
see the later for more info.
Set
`javascript
require('ig-types/Set')
`
$3
Unite two sets and return the resulting set
`bnf
.unite()
->
`
This is a shorthand for new Set([..., ...])
$3
Intersect two sets and return the intersection set
`bnf
.untersect()
->
`
$3
Subtract from set and return resulting set
`bnf
.subtract()
->
`
$3
In-place splice a set
`bnf
.splice()
.splice(, )
.splice(, , ...)
->
`
This is the same as
.splice(..)
but without the ability to add more than one instance of an item.
$3
Replace value in set with other value retaining item order (in-place)
`bnf
.replace(, )
.replace(, , true)
->
`
Replace the value without sorting
`bnf
.replace(, , false)
->
`
Note that when sorting large sets this can get expensive.
$3
Replace item at position in set retaining order (in-place)
`bnf
.replaceAt(, )
.replaceAt(, , true)
->
`
If is less than 0 the item will be prepended to ,
if the is greater than or equal to .size then is
appended.
Replace the value at index without sorting
`bnf
.replaceAt(, , false)
->
`
Here, if is less than 0 or greater than or equal to .size
will always be appended to .
Note that when sorting large sets this can get expensive.
$3
Sort (in-place)
`bnf
.sort()
->
.sort()
->
`
In the general case this is similar to
.sort(..)
with the addition of the .sortAs(..)'s ability to sort
as a list
`bnf
.sort()
->
`
This is similar to .sort(..) and Object.sort(..),
see the later for more info.
$3
For more info see corresponding stoppable methods in
Array's section.
Date
`javascript
require('ig-types/Date')
`
$3
Generate a timestamp (format: 'YYYYMMDDHHMMSS')
`bnf
Date.timeStamp()
->
`
Generate a full timestamp, including milliseconds (format: 'YYYYMMDDHHMMSSmmm')
`bnf
Date.timeStamp(true)
->
`
This is a shorthand to: (new Date()).getTimeStamp(..)
The timestamp is generated from the time of call, for generating timestamps form specific objects see:
.getTimeStamp(..)
$3
Create a from a timestamp
`bnf
Date.fromTimeStamp()
->
`
This is a shorthand to: (new Date()).setTimeStamp()
$3
Convert a string describing a time period into milliseconds.
`bnf
Date.str2ms()
->
`
Examples:
`javascript
// time units (d/h/m/s/ms) and their variants...
var a = Date.str2ms('3 seconds') // -> 3000
var b = Date.str2ms('0.1h') // -> 360000
// time period (DD:HH:MM:SS:mmm)...
var c = Date.str2ms('00:20:001') // -> 20001
var d = Date.str2ms('1:3') // -> 63000
`
Note that time periods are seconds-based by default unless it contains three
digits then it defaults to milliseconds:
`javascript
// least significant unit is seconds by default...
var e = Date.str2ms(':3') // -> 3000
// when the least significant unit contains 3 digits it is read as ms...
var f = Date.str2ms(':030') // -> 30
`
Supported formats:
`bnf
::=
|
|
|
|
|
::=
| ms
| m[illi][-]s[ec[ond[s]]]
::=
s
| s[ec[ond[s]]]
::=
m
| m[in[ute[s]]]
::=
h
| h[our[s]]
::=
d
| d[ay[s]]
::=
[[[DD:]HH:]MM]:SS[:mmm]
| [[[[DD:]HH:]MM]:SS]:mmm
`
$3
Generate a short date string from
(format: 'YYYY-MM-DD HH:MM:SS')
`bnf
.toShortDate()
->
`
Generate a short date string including milliseconds from
(format: 'YYYY-MM-DD HH:MM:SS:mmm')
`bnf
.toShortDate(true)
->
`
Note that is directly parseable by new Date(..)
`javascript
var a = (new Date()).toShortDate(true)
// parse the and generate a new short date from it...
var b = (new Date(a)).toShortDate(true)
a == b // -> true
`
$3
Generate a timestamp from
(format 'YYYYMMDDHHMMSS')
`bnf
.getTimeStamp()
->
`
Generate a timestamp from including milliseconds
(format 'YYYYMMDDHHMMSSmmm')
`bnf
.getTimeStamp(true)
->
`
$3
Update a from a timestamp
`bnf
.setTimeStamp()
->
`
String
`javascript
require('ig-types/String')
`
$3
Capitalize the first character of a string
`bnf
.capitalize()
->
`
$3
Indent each line in by spaces
`bnf
.indent()
->
`
Indent/prepend each line in by the string
`bnf
.indent()
->
`
RegExp
`javascript
require('ig-types/RegExp')
`
$3
Quote regexp reserved characters in a string
`bnf
RegExp.quoteRegExp()
->
`
This is mainly used to quote strings to be matched as-is within a regular expression.
Promise
`javascript
require('ig-types/Promise')
`
or
`javascript
var promise = require('ig-types/Promise')
`
$3
_Interactive promises_ can be sent messages and then handle them.
`javascript
var printer = Promise.interactive(function(resolve, reject, onmessage){
var buf = []
var state = 'pending'
onmessage(function(type, ...args){
type == 'flush' ?
(buf = buf
.filter(function([type, state, ...args]){
consoletype :, ...args) }))
: type == 'close' ?
(resolve(...args),
state = 'resolved')
: buf.push([type, state, ...args]) }) })
printer
.send('log', 'some message...')
.send('warn', 'some warning...')
.send('flush')
.send('close')
`
Note that message handling is independent of promise state, so in the above case
we can still populate the buffer and _flush_ it even if the promise is resolved
`javascript
printer
.send('log', 'some other message...')
.send('flush')
`
If the user wants to handle messages differently (ignore for example) after the
promise is finalized it is their responsibility
(see: (..) for more info)
#### Promise.interactive(..)
Create and interactive promise
`bnf
Promise.interactive()
->
`
The accepts one additional argument, compared to the Promise(..)
handler, , used to register message handlers.
`bnf
(, , )
()
`
Remove
`bnf
(, false)
`
Remove all handlers
`bnf
(false)
`
is called when a message is sent via
.send().
#### .send(..)
Send a message to an interactive promise
`bnf
.send()
.send(...)
->
`
Sending a message triggers message handlers registered via (..)
passing each handler the arguments.
#### .then(..)
Extended .then(..) implementation.
See .then(..) for details.
$3
A _cooperative promise_ is one that can be finalized externally/cooperatively.
This can be useful for breaking recursive dependencies between promises or when
it is simpler to thread the result receiver promise down the stack than building
a promise stack and manually threading the result up.
Example:
`javascript
// NOTE: implementing this via Promise.any(..) would also require implementing a
// way to stop the "workers" after the result is found...
async function controller(trigger){
while(!trigger.isSet)
// do things...
trigger.isSet
|| trigger.set(result) } }
async function controlled(trigger){
// do things independently of trigger...
trigger
.then(function(){
// do things after trigger...
}) }
var t = Promise.cooperative()
// multiple cooperative controllers competing to create a result...
controller(t)
controller(t)
controller(t)
// ...
// prepare and process result...
// NOTE: calling .then() here is completely optional and done out of role
// hygene -- isolating cooperative API from the client...
controlled(t.then())
// ...
`
Note that this functionally can be considered a special-case of an
interactive promise , but in reality they are two
different implementations, the main differences are:
- _Cooperative promise_ constructor does not need a resolver function,
- _Cooperative promises_ do not the implement .send(..) API.
Note that implementing _Cooperative promises_ on top of _Interactive promises_
cleanly, though feeling more _"beautiful"_, would be more complex than the
current standalone implementation, as it would require both implementing
the .set(..) API/logic _and_ active encapsulation of the message API.
#### Promise.cooperative()
Create a cooperative promise
`bnf
Promise.cooperative()
->
`
#### .set(..)
Resolve with
`bnf
.set()
.set(, true)
->
`
If is a promise, then will be bound to its state, i.e.
resolved if is resolved and rejected if it is rejected with the same
values.
Reject with
`bnf
.set(, false)
->
`
Calling .set(..) will set .isSet to true.
#### .isSet
Property representing if the cooperative promise was _set_ / .set(..) was
called (value is true) or no (false).
This property is read-only.
#### .then(..)
Extended .then(..) implementation.
See .then(..) for details.
$3
An _iterable promise_ is on one hand very similar to Promise.all(..) in that it
generally takes a list of values each could be either an explicit value or a
promise, and it is similar to a _generator_ in that it allows iteration over the
contained values and chaining of operations but unlike Promise.all(..) this
iteration occurs depth-first instead of breadth first.
One can think of _promise iterators_ vs. _generators_ as the former being
internally controlled and asynchronous while the later being externally
controlled and synchronous.
Here is a traditional example using Promise.all(..):
`javascript
var p = Promise.all([ .. ])
// this will not execute until ALL the inputs resolve...
.then(function(lst){
return lst
.filter(function(e){
// ...
})
// this will not run until ALL of lst is filtered...
.map(function(e){
// ...
}) })
`
And a _promise iterator_:
`javascript
var p = Promise.iter([ .. ])
// each element is processed as soon as it is ready disregarding of its order
// in the input array...
.filter(function(e){
// ...
})
// items reach here as soon as they are returned by the filter stage handler...
.map(function(e){
// ...
})
// .then(..) explicitly waits for the whole list of inputs to resolve...
.then(function(lst){
// ...
})
`
This approach has a number of advantages:
- items are processed as soon as they are available without waiting for the
slowest promise on each level to resolve
- simpler and more intuitive code
And some disadvantages:
- item indexes are unknowable until all the promises resolve.
Calling each of the methods will return a new and unresolved
promise, even if the original is resolved.
If all values are resolved the will resolve on the next
execution frame.
There are two types of iterator methods here, both are transparent but different
in how they process values:
- Parallel methods
These handle elements as soon as they are available even if the parent promise
is not yet resolved.
- Proxies
These methods simply wait for the main promise to resolve and then call the
appropriate method on the result.
Promise iterators directly support _for-await-of_ iteration:
`javascript
for await (var elem of Promise.iter(/ ... /)){
// ...
}
`
Promise iteration supports three modes of synchronization:
1. handle on ready
`
.iter(
[value, promise, promise, value], handler)
+ . . +
| R |
+ R | +
| +
+ - - - - - - - - - - - - - - - -> resolve
R - input resolved
`
A handler is started as soon as it's value is ready/resolved, i.e.
for non-promise values start immediately.
2. handle sequentially when value is ready and previous handler is started
`
.seqstartiter(
[value, promise, promise, value], handler)
+ . . . |
| R <-+
++- - >R . . |
| | <-+
+ ++ - - - >+ . |
. | | <-+
+ + - - > + |
. . | <-+
+ - - - - - - - > resolve
^ ^
+------+-- returned promise
R - input resolved
`
A handler is started as soon as all previous handlers are started
and the current value is ready/resolved.
3. handle sequentially when value is ready and previous handler is resolved
`
.seqiter(
[value, promise, promise, value], handler)
+ . . . |
| R <-+
++- - >R . . |
| | <-+
+ ++ . . |
. | |
+ - - - >+ . |
. . | <-+
+ - - > + |
. . | <-+
+ - - - - - - - > resolve
^ ^
+------+-- returned promise
R - input resolved
`
A handler is started as soon as all previous handlers are done, their
return values are resolved and the current value is ready/resolved.
#### Promise.iter(..) / promise.IterablePromise(..)
Create an _iterable promise_
`bnf
Promise.iter()
Promise.iter()
->
`
#### .iter()
Wrap a promise in an promise iterator.
`bnf
.iter()
->
`
If resolves to a non-array value it will be treated as a single
element, otherwise the array will be iterated over.
#### .iter()
Return a shallow copy of the current promise iterator.
`bnf
.iter()
->
`
#### Promise.seqiter(..) / promise.IterableSequentialPromise(..)
#### .seqiter() / .seqiter()
#### Promise.seqstartiter(..) / promise.IterableSequentialStartPromise(..)
#### .seqstartiter() / .seqstartiter()
#### .map(..) / .filter(..) / .reduce(..)
Methods similar but not fully equivalent to Array's
.map(..),
.filter(..),
and .reduce(..)
`bnf
.map()
->
()
->
`
`bnf
.filter()
->
()
->
`
`bnf
.reduce(, )
->
(, )
->
`
Note that these are different to Array's equivalents in some details:
- is _not_ called in the order of element occurrence but rather
in the order of elements are resolved/ready.
- does not get either the element _index_ or the _container_.
this is because in _out-of-order_ and _depth-first_ execution the
index is _unknowable_ and the container is a promise/black-box.
This is especially critical for .reduce(..) as the iteration in an order
different from the order of elements _can_ affect actual result if this is
not expected.
.reduce(..) is also a bit different here in that it will return a basic
rather than an iterable promise object as we can't know what
will it will reduce to.
Note that since .reduce(..) handler's execution order can not be known,
there is no point in implementing .reduceRigth(..).
#### .between(..)
`bnf
.between()
->
.between()
->
([, ], , , )
->
`
#### .flat(..)
`bnf
.flat()
.flat()
->
`
This is similar to .flat(..) see it for more info.
#### .reverse()
`bnf
.reverse()
->
`
This is deferent from .reverse() in that it will _not_ reverse in-place,
but rather a _reversed copy_ will be created.
This is similar to .reverse() see it for more info.
#### .concat(..)
`bnf
.concat()
->
`
This is similar to .concat(..) see it for more info.
#### .push(..) / .unshift(..)
`bnf
.push()
->
.unshift()
->
`
These are similar to .push(..)
and .unshift(..)
see them for more info.
#### .at(..) / .first() / .last()
Proxies to the appropriate array methods with a special-case: when getting elements
at positions 0 or -1 (i.e. .first() / .last()) these _can_ resolve before the
parent .
XXX
#### .join(..)
XXX
#### .some(..) / .find(..)
`bnf
.some()
->
.find()
->
`
The main difference between .some(..) and .find(..) is in that the
returned from the former will resolve to either true or false, and in the later
to the found value or undefined.
.find(..) supports an additional argument that controls what returned
is resolved to...
`bnf
.find()
.find(, 'value')
->
.find(, 'bool')
->
.find(, 'result')
->
`
- value (default)
resolve to the stored value if found and undefined otherwise.
- bool
resolve to true if the value is found and false otherwise, this is how
.some(..) is impelemnted.
- result
resolve to the return value of the test .
These are similar to .some(..)
and .find(..)
see them for more info.
#### Array proxy methods returning
- .sort(..)
- .slice(..)
- .entries() / .keys() / .values()
These methods are proxies to the appropriate array methods.
`bnf
.(..)
->
`
These methods need the parent to resolve before resolving themselves.
XXX links...
#### Array proxy methods returning a
- .indexOf(..)
- .includes(..)
- .every(..)
- .findIndex(..)
These methods are proxies to the appropriate array methods.
`bnf
.(..)
->
`
These methods need the parent to resolve before resolving themselves.
Since the equivalent array methods do not return iterables these will return a basic
(non-iterable) .
XXX links...
#### .then(..) / .catch(..) / .finally(..)
An extension to
.then(..) API
this adds the ability to pass no arguments
`bnf
.then()
->
`
This will return a generic promise wrapper passing through the results as-is. This
can be useful to hide the extended promise API from further code.
#### promise.IterablePromise.STOP / promise.IterablePromise.STOP(..)
A special object that when thrown from a function/promise handler will stop
further iteration.
This is undefined until the ig-types/Array module is loaded.
For more info see: Stopping the iteration below, and
the 'Array' STOP section
#### .iterthen(..)
Like .then(..) but will return an IterablePromise instance.
#### .isSync()
Return true if all content is resolved, otherwise return false.
#### .sync(..)
If all content is resolved return the promise value, otherwise return a promise.
For more info see: .sync(..)
#### Advanced handler
`bnf
Promise.iter(, )
->
`
The will get passed each resolved of the input
as soon as it's available/resolved.
The return value is unwrapped into the resulting array, allowing
each call to both remove elements (i.e. returning []) from the resulting
as well as insert multiple items (by returning an array of items).
`bnf
()
-> []
-> [ , .. ]
->
`
`bnf
::=
[]
| [ , .. ]
::=
| ()
`
Example:
`javascript
var p = Promise.iter(
[1, 2, 3, Promise.resolve(4), [5, 6]],
function(elem){
// duplicate even numbers...
return elem % 2 == 0 ?
[elem, elem]
// return arrays as-is...
: elem instanceof Array ?
[elem]
// remove other elements...
: [] })
.then(function(lst){
console.log(lst) }) // -> [2, 2, 4, 4, [5, 6]]
`
#### Stopping the iteration
Like the Array module, this support throwing STOP to
stop iteration. As we uses .smap(..)
stopping support is supported if ig-types/Array module is loaded.
`javascript
require('ig-types/Array')
`
This is also different semantically, as promise iteration can happen out of order,
stopping affects the order of processing and not order of the input array with one exception: promises already created can not be stopped in JavaScript.
Any handler function passed to a method can throw a STOP.
For more details see: the 'Array' STOP section
$3
_Promise proxies_ generate a set of prototype methods returning promises that when the parent promise is resolved will resolve to a specific method call.
Example:
`javascript
var o = {
method: function(...args){
console.log('method:', ...args)
},
}
var p = Peomise.cooperative().as(o)
p.method(1, 2, 3) // returns a promise...
// ...
// resolving a promise will trigger all the proxy emthod execution, so
// here 'method: 1, 2, 3' will get printed...
p.set(o)
`
#### .as(..)
Create a promise proxy
`bnf
.as()
.as()
->
`
A proxy promise will be populated with proxy methods to all the methods of the or .prototype.
#### .(..)
When resolves, call the .(..) on the resolved value.
`bnf
.(..)
->
`
will resolve the the return value of the when
the main is resolved.
$3
The goal of this is to handle both sync and asynchronous data flows with
one promise-like API, if all of the data can be obtained in a sync manner
this will be sync otherwise we will revert to a normal promise.
#### Promise.sync(..) / promise.SyncPromise(..)
`dnf
Promise.sync()
->
->
(, )
()
()
`
Implements the full Promise protocol but does it in a sync manner, but
the execution of is done synchronously. If the value passed to
(..) is a promise this will return that and continue asynchronously
otherwise all the promise API (.then(..)/.catch(..)/...) is run in sync.
#### .sync(..)
Synchronously return the resolved value if resolved, and
if it _rejected_ then re-throw the . Normal promises will return self.
`bnf
.sync()
->
->
`
To suppress errors pass false to .sync(..) and to handle them differently
pass an error handler function.
`bnf
.sync(false)
->
.sync()
->
()
->
`
#### .value / .error
attributes that provide access the resolved .value and/or
rejection .error.
#### Promise.sync.all(..) / Promise.sync.allSettled(..) / Promise.sync.any(..) / Promise.sync.race(..)
Equivalents to Promise's respective versions but will run sync if the
relevant items in the input are either non-promises or s.
$3
#### Promise.awaitOrRun(..)
Await for inputs if any of them is a promise and then run a function with
the results, otherwise run the function in sync.
`dnf
Promise.awaitOrRun(, [, ])
Promise.awaitOrRun(, .. , [, ])
->
->
`
Note that if the last is a function and no function
is given then .awaitOrRun(..) will confuse the for ,
to avoid this one needs to explicitly pass null/undefined as .
_Special-case: this will expand async generators if they define .then(..),
this may change in the future._
Generator extensions and utilities
`javascript
var generator = require('ig-types/generator')
`
$3
The _generator_ hierarchy in JavaScript is a bit complicated.
Consider the following:
`javascript
// generator constructor function...
var Iter = function*(L){
for(var e of L){
yield e }}
// generator instance...
var iter = Iter([1, 2, 3])
`
We can test that iter is an instance of Iter:
`javascript
iter instanceof Iter // -> true
`
Note that there is no generator constructor constructor or _meta-generator_,
i.e. Iter is created syntactically and not constructed via a new _constructor_.
Due to the three level structure of generators we use a slightly different
terminology to reference different levels of API's:
- Generator - the generator meta-constructor.
This is a constructor that is used to create/prototype 's, i.e.
generator constructors.
Generator is mainly used for instanceof checks, but can be used as a
prototype for extending generators.
- - the generator constructor.
This is the product of either a Generator meta-constructor or a
function*(..){ .. } statement.
In the above example Iter is a generator constructor.
- - the generator instance.
Generator instances are created by calling a / generator
constructor.
In the above example iter is a generator instance.
Iterators and generators are similar but not the same. Some objects like Array's,
Map's and Set's provide a number of generic iterators that are not implemented
as generators. These objects are also extended by ig-types/generator to match the
object API defined below.
#### generator.Generator
Exposes the _hidden_ JavaScript generator meta-constructor.
This is similar to the JavaScript's
[Function`](https://developer.mozilla.org