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.