Pseudo-Parallel, Passing-Procedure, Pretty-Promise. Asynchronous Collection & Procedure Control Flow
pp.js is called pianissimo.js, which means Pseudo-Parallel, Passing-Procedure, or Pretty-Promise. pp.js is a javascript library for Asynchronous Collection & Procedure Control Flow.
this library is inspired by async.js, JsDeferred, $.Deferred, and Promise/A. And aiming provide compatible API.
to read this library specification see Guide, Reference
MIT License. see LICENSE file.
These function create instance of Promise
and Generator
that controller
about Asynchonouse routine.
These API are very similar to functions of async.js. But here is different argument rule, never blocking user thread, and faster.
These API are very similar to functions of async.js.
But here is different argument rule, never blocking user thread, never occuring
Stack Over Flow
, faster (x 1.5 ~ 2.0), and use less heap memory (x ~0.5).
process.nextTick
(node.js) or
setTimeout(fn, 0, args...)
)pp.js is designed by CPS, Continuation Passing Style, for effective Asynchronous processing.
# sync procedure
sq = (x) ->
x * x
console.log sq 10 # return 10 * 10 -> 100 -> console.log(100) => IO output
# CPS procedure
cpsSq = (next, x) ->
next x * x
cpsSq console.log, 10 # console.log(10 * 10) -> console.log(100) => IO output
# Async procedure
heavyProcessing = (callback, parameters) ->
# do something (use long time, or network communication)
# ...
# ...
callback error, result # when process done, result apply asynchronouse
heavyProcessing (e, r) -> # callback
if e # receive error
# do something
else # process has been succeeded
# do something
, [### parameters ###]
pp.js doesn't provide true parallel processing. Parallel processing is a strictly pseudo. This pseudo-parallel processing on Trampoling.
pp.js API are curried function.
For example, CPS sum of number array is this.
printSum = pp.foldl (next, memo, value) ->
if typeof value isnt 'number'
next new TypeError "\"folding\" require number, but #{typeof value}"
else
next null, memo + value
return
, (error, result) ->
console.log result
, 0 # See it! subject array has not apply!
printSum [10, 11, 12] #=> 33
printSum [1, 2, 3, 4, 5] #=> 15
In designing Asynchronous operetion, maybe occur a problem that dependency with each procedures.
Because solve it, pp.js provide two iteration. fill and order.
fill
process is ASAP (As Soon As Possible)
fireStack = []
pp.fill [
(next) ->
setTimeout ->
fireStack.push '1st'
next null, '1st'
, 100
, (next) ->
setTimeout ->
fireStack.push '2nd'
next null, '2nd'
, 200
, (next) ->
setTimeout ->
fireStack.push '3rd'
next null, '3rd'
, 50
], (error, result) ->
# result --- ['1st', '2nd', '3rd']
# fire_stack --- ['3rd', '1st', '2nd']
order
process is keep invocation order.
fireStack = []
pp.order [
(next) ->
setTimeout ->
fireStack.push '1st'
next null, '1st'
, 100
, (next) ->
setTimeout ->
fireStack.push '2nd'
next null, '2nd'
, 200
, (next) ->
setTimeout ->
fireStack.push '3rd'
next null, '3rd'
, 50
], (error, result) ->
# result --- ['1st', '2nd', '3rd']
# fire_stack --- ['1st', '2nd', '3rd']
pp.fill F, G, H, CALLBACK
# eval F -> eval G -> eval H -> (wait callback...) -> eval CALLBACK
pp.order F, G, H, CALLBACK
# eval F -> (wait F callback...) -> eval G -> (wait G callback...) -> ...
Why pp.fill
's name is parallel but fill? Because it run all procedures and wait until all callback is filling.
pp.order
is keeping its ordering. When it began run procedure, wait that callback, run next procedure. Until last.
One of difference between pp.js with async.js is consisted argument format.
pp.TIME_SLICE
provide consts for frame rate.
pp.js defined Callback type that is function(Error, [somethings...])
.
first argument, received Error, is accepted as nullable.
pp.js defined Iterable type that is not null Array or Object.
primitive values ... undefined
, null
, string
, boolean
and number
aren't accepted.
pp.js defined Iterator type that is function(callback, [somethings...])
For example, available iterator for Array
function(function:next, any:value, number:index, array:iterable)
function(function:next, any:value, number:index)
function(function:next, any:value)
function(function:next)
for Object,
function(function:next, any:value, string:key, object:iterable)
function(function:next, any:value, string:key)
function(function:next, any:value)
function(function:next)
iterator type need continuation function for 1st argument.
pp.js defined Predicator type that is function(callback, value, [key, iterable])
Specially, predicator passing boolean
result to callback.
cpsIsEven = (next, value) ->
next null, value % 2 is 0
pp.js defined Folding type that is function(callback, memo, value, [key, iterable])
.
for accumulate array list.
cpsAdd = (next, memo, value) ->
next null, memo + value
|||documentation writing now...|||
current = ''
iter = pp.iterator [
->
current = '1st'
, ->
current = '2nd'
, ->
current = '3rd'
]
iter2 = iter()
console.log current # '1st'
iter3 = iter2()
console.log current # '2nd'
iter3()
console.log current # '3rd'
iter4 = iter.next()
iter4()
console.log current # '2nd'
pp.waterfall [
(next) ->
next null, 1
, (next, v) ->
next null, v, v * 2 # {v: 1}
, (next, v1, v2) ->
next null, v1 + v2 # {v1: 1, v2: 2}
], (error, result) ->
console.log error is null # true
console.log result # 3
fireStack = []
pp.fill [
(next) ->
setTimeout ->
fireStack.push '1st'
next null, '1st'
, 100
, (next) ->
setTimeout ->
fireStack.push '2nd'
next null, '2nd'
, 200
, (next) ->
setTimeout ->
fireStack.push '3rd'
next null, '3rd'
, 50
], (error, result) ->
# result --- ['1st', '2nd', '3rd']
# fire_stack --- ['3rd', '1st', '2nd']
fireStack = []
pp.order [
(next) ->
setTimeout ->
fireStack.push '1st'
next null, '1st'
, 100
, (next) ->
setTimeout ->
fireStack.push '2nd'
next null, '2nd'
, 200
, (next) ->
setTimeout ->
fireStack.push '3rd'
next null, '3rd'
, 50
], (error, result) ->
# result --- ['1st', '2nd', '3rd']
# fire_stack --- ['1st', '2nd', '3rd']
{{string: *}}
)pp.each (next, value, index, itrable) ->
# do something
if errorCondition
# when it should throw Error, instead of call
next new Error "error"
else if haltCondition
# when it should halt iteration (purpose has been achieved)
next(null, result);
else # call iteration callback simply
next()
, (error) ->
# do something when finish (or halt) iteration
, ['a.coffee', 'b.coffee', 'c.coffee']
pp.eachOrder
is another version of pp.each
that keep invocation callback order.
{{string: *}}
)cpsSqMap = pp.map (next, value, index) ->
if typeof value isnt 'number'
next new TypeError "cpsSqMap require number array.
but include #{typeof value} (#{value}) at [#{index}]"
else
next null, value * value
cpsSqMap console.log, [1, 2, 3, 4, 5] #=> null [1, 4, 9, 16, 25]
cpsSqMap console.log, [1, 2, '3', 4, 5]
#=> [TypeError: cpsSqMap require number array. but include string (3) at [2]] [ 1, 4 ]
pp.mapOrder
is another version of pp.each
that keep invocation callback order.
pp.filter
's invocation is order
{{string: *}}
)cpsOdd = (next, value) ->
if typeof value isnt 'number'
next new TypeError "cpsOdd require number array. but include
#{typeof value} (#{value}) at [#{index}]"
else
next null, value % 2 is 1 # apply 2nd arg as boolean
printCallback = (error, results) ->
console.log if error then error.message else results
pp.filter cpsOdd, printCallback, [1, 2, 3, 4, 5]
#=> [1, 3, 5]
pp.filter cpsOdd, printCallback, [2, 4, 6, 8, 10]
#=> []
cpsPrivate = (next, value, key) ->
next null, key.match /^_/
# filtering to hashmap
pp.filter cpsPrivate, printCallback,
name: 'John'
age: 26
gender: MALE
_hasGirlFriend: yes
#=> "{_hasGirlFriend: true}" (o_O)
complement of pp.filter
{{string: *}}
)pp.reject cpsOdd, printCallback, [1, 2, 3, 4, 5]
#=> [2, 4]
pp.reject cpsOdd, printCallback, [10, 12, 14, 16, 18]
#=> [10, 12, 14, 16, 18]
# filtering to hashmap
pp.reject cpsPrivate, printCallback,
name: 'John'
age: 26
gender: MALE
_hasGirlFriend: yes
#=> "{name: 'John', age: 26, gender: "male"}" (-_-)
lookup match value from iterable.
{{string: *}}
)pp.find cpsOdd, printCallback, [1, 2, 3, 4, 5]
#=> 1
pp.find cpsOdd, printCallback, [10, 12, 14, 16, 18]
#=> undefined
pp.find (next, value, key) ->
next null, key.match /^#[a-zA-Z0-9]/
, (error, value, key) ->
console.log "value: #{value}, key: #{key}"
, # js Object as CSS
body:
width: '100%'
'#container':
'background-color': '#eee'
'.notice':
color: '#000'
#=>value: {'background-color': '#eee'} key: '#container'
pp.any
is CPS Array.some
{{string: *}}
)pp.any cpsOdd, printCallback, [0, 2, 5, 8, 10]
#=> true
pp.any cpsOdd, printCallback, [2, 4, 6, 8, 10]
#=> false
pp.all
is CPS Array.every
{{string: *}}
)pp.all cpsOdd, printCallback, [1, 3, 6, 7, 9]
#=> false
pp.all cpsOdd, printCallback, [1, 3, 5, 7, 9]
#=> true
folding accumulation left(first of array) to right(last of array).
pp.foldl
's invocation is order
pp.foldl (next, r, x) ->
next null, r + x
, (error, result) ->
console.log result # => 15
, 0, [1, 2, 3, 4, 5] # 0 + 1 + 2 + 3 + 4 + 5 => 15
pp.foldl1
require Array has 1 or more length. use first element from Array as init
value.
pp.foldl1 (next, r, x) ->
next null, r + x
, (error, result) ->
console.log result # => 15
, [1, 2, 3, 4, 5] # 1 + 2 + 3 + 4 + 5 => 15
pp.foldl1 (next, r, x) ->
next null, r + x
, (error, result) ->
console.log error # => TypeError
, [] # empty array :^(
folding accumulation right(last of array) to left(first of array).
pp.foldl
's invocation is order
pp.foldr (next, r, x) ->
next null, r + x
, (error, result) ->
console.log result # => 15
, 0, [1, 2, 3, 4, 5] # 0 + 5 + 4 + 3 + 2 + 1 => 15
pp.foldr1
require Array has 1 or more length. use last element from Array as init
value.
pp.foldr1 (next, r, x) ->
next null, r + x
, (error, result) ->
console.log result # => 15
, [1, 2, 3, 4, 5] # 5 + 4 + 3 + 2 + 1 => 15