ECMAScript proposal: Emitter

  • JavaScript's most general container, over single and multiple, sync and async values
  • Operators form the Generics, a large part of the eventual standard library
  • Composable algorithmic transformations, decoupled from their input and outputs
  • Well-integrated with language rather than inventing lots of new concepts, (i.e. Promises, iteration, async iteration, etc)
  • Backwards-compatible unification of platform API (interfaces like EventTarget and EventEmitter)
  • Ultra-High Performance (faster than most.js) and memory efficient

Reactive programming examples

Log XY coordinates of click events on <button> elements:

const { on, map, filter, run } = Emitter

run(
  on(document, 'click')
, filter(ev => ev.target.tagName === 'BUTTON')
, map(ev => ({ x: ev.clientX, y: ev.clientY }))
, coords => console.log(coords)
)

Map clicks from two buttons to a stream of +1 and -1, merge them and render the result:

const { on, map, tap, reduce, flatten, render } = Emitter

const inc = map(() => 1, on(buttonA, 'click')) 
const dec = map(() => -1, on(buttonB, 'click'))
const counter = reduce(0, flatten(inc, dec))
const render = tap(total => { output.innerText = String(total) }, counter)
run(render)

Fork 10 peers, wait till they've all connected, return a reference to the processes:

const { run, until, once, flatten, reduce } = Emitter
const { fork } = require('child_process')

const processes = await run(
  10
, map(() => fork('peer.js'))
, map(async peer => {
    await until(d => d == 'connected', on(peer, 'message'))
    return peer
  })
, flatten()
, reduce([])
)

Iterable programming examples

From a range of numbers, divide them by 4, remove any greater than 12, then start collecting those one by one:

function* range(from, to) { 
  for (let i = from; i <= to; i++) yield i  
}

const arr = run(
  range(40, 60)
, map(d => d / 4)
, filter(d => d < 12)
, reduce([])
)

Transform a series of numbers one by one and log the result:

for (const d of map(d => d + 1, [1, 2, 3]))
  console.log('transformed output', d)

Log whenever the user presses CTRL+C

for await (const d of filter(([n]) => n == 3, process.stdin))
  console.log('SIGINT!')

API

Transformation

Filtering

Running

Creating


Background

For general background into this space, especially from more of a language perspective, Rich Hickey's talk on the motivation, design and use of Clojure's core.async library is a must.

For comparison, here would be Rob Pike's original example of reducing tail latency using replicated search servers discussed in the talk:

const { race, timeout, until, reduce, flatten } = Emitter 

const results = await race(
  timeout(80)
, until(3, reduce([], flatten(
    race(web1(query), web2(query))
  , race(img1(query), img2(query))
  , race(vid1(query), vid2(query))
  )))
)

It's useful to also familiarise with existing JavaScript libraries in this space:

More details are included and will be documented in the FAQ.