pp.js

Pseudo-Parallel, Passing-Procedure, Pretty-Promise. Asynchronous Collection & Procedure Control Flow

View the Project on GitHub VoQn/pp.js

pp.js - pianissimo -

Build Status

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

Faster, Fewer Cost, Parallel multi process

Benchmark pp.js vs async.js

License

MIT License. see LICENSE file.

API

Continuation Object

These function create instance of Promise and Generator that controller about Asynchonouse routine.

Control Flow against Array as procedures

These API are very similar to functions of async.js. But here is different argument rule, never blocking user thread, and faster.

Collection API

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).

Plugin Extention Interface, and Etc...


Guide

CPS (Continuation Passing Style)

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 ###]

Trampolining, "Pseudo-Parallel"

pp.js doesn't provide true parallel processing. Parallel processing is a strictly pseudo. This pseudo-parallel processing on Trampoling.

Public API are curried

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

Invocation type of callback, "fill" and "order"

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

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

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']

Difference?

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.

Type

One of difference between pp.js with async.js is consisted argument format.

TimeSlice: number (integer milli-second [0 < t])

pp.TIME_SLICE provide consts for frame rate.


Callback: function(?Error, [somethings...])

pp.js defined Callback type that is function(Error, [somethings...]).

first argument, received Error, is accepted as nullable.


Iterable: (!Array | !Object as {string: any})

pp.js defined Iterable type that is not null Array or Object.

primitive values ... undefined, null, string, boolean and number aren't accepted.


Iterator: function(callback, [somethings...])

pp.js defined Iterator type that is function(callback, [somethings...])

For example, available iterator for Array

for Object,

iterator type need continuation function for 1st argument.


Predicator: function(callback, value, [key, iterable])

pp.js defined Predicator type that is function(callback, value, [key, iterable])

Specially, predicator passing boolean result to callback.

Example

cpsIsEven = (next, value) ->
  next null, value % 2 is 0

Accumulator: function(callback, memo, value, [key, iterable])

pp.js defined Folding type that is function(callback, memo, value, [key, iterable]).

for accumulate array list.

Example

cpsAdd = (next, memo, value) ->
  next null, memo + value

Referrence

|||documentation writing now...|||

Control Flow

pp.iterator(procs)

Arguments

Example

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(procs, callback, [timeSlice])

Arguments

Example

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

pp.whilist(predicator, iterator, callback, init, [timeSlice])


pp.until(predicator, iterator, callback, init, [timeSlice])


pp.fill(procs, callback, [timeSlice])

Arguments

Example

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']

pp.order(procs, callback, [timeSlice])

Arguments

Example

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']

Collection API

pp.each(iterator, callback, iterable, [timeSlice])

Arguments

Example

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(iterator, callback, iterable, [timeSlice])

pp.eachOrder is another version of pp.each that keep invocation callback order.


pp.map(iterator, callback, iterable, [timeSlice])

Arguments

Example

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(iterator, callback, iterable, [timeSlice])

pp.mapOrder is another version of pp.each that keep invocation callback order.


pp.filter(predicator, callback, iterable, [timeSlice])

pp.filter's invocation is order

Arguments

Example

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)

pp.reject(predicator, callback, iterable, [timeSlice])

complement of pp.filter

Arguments

Example

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"}" (-_-)

pp.find(predicator, callback, iterable, [timeSlice])

lookup match value from iterable.

Arguments

Example

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(predicator, callback, iterable, [timeSlice])

pp.any is CPS Array.some

Arguments

Example

pp.any cpsOdd, printCallback, [0, 2, 5, 8, 10]
#=> true
pp.any cpsOdd, printCallback, [2, 4, 6, 8, 10]
#=> false

pp.all(predicator, callback, iterable, [timeSlice])

pp.all is CPS Array.every

Arguments

Example

pp.all cpsOdd, printCallback, [1, 3, 6, 7, 9]
#=> false
pp.all cpsOdd, printCallback, [1, 3, 5, 7, 9]
#=> true

pp.foldl(accumulator, callback, init, array, [timeSlice])

folding accumulation left(first of array) to right(last of array).

pp.foldl's invocation is order

Arguments

Example

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(accumulator, callback, array, [timeSlice])

pp.foldl1 require Array has 1 or more length. use first element from Array as init value.

Arguments

Example

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 :^(

pp.foldr(accumulator, callback, init, array, [timeSlice])

folding accumulation right(last of array) to left(first of array).

pp.foldl's invocation is order

Arguments

Example

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(accumulator, callback, array, [timeSlice])

pp.foldr1 require Array has 1 or more length. use last element from Array as init value.

Arguments

Example

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