A signals library for functional reactive programming. Author: Tim Farland
- Inspired by Elm and Bacon.js.
- Written without the use of
this
,new
, orprototype
- only simple objects and functions. - Miniscule size - ~2kb minified/gzipped.
- For modular use in node or browsers.
- Written in Typescript.
- License: MIT.
npm install --save acto
npm test
import { create, listen, send /* etc */ } from 'acto'
Signals are simple objects with the following interface:
interface Signal<T> {
listeners: Array<(T) => any>
active: boolean
value: T | null
stop?: Function
}
Capture events on a dom node.
// fromDomEvent (node: Node, eventName: string): Signal<Event>
const clicks = fromDomEvent(document.body, "click", evt => console.log(evt.target))
A signal that will emit one value, then terminate.
// fromCallback<T> (f: Callback<T>): Signal<T>
const later = fromCallback(callback => setTimeout(() => callback("Finished"), 1000))
A signal that will emit one value or an error from a Promise, then terminate.
// fromPromise (promise: Promise<any>): Signal<any>
const wait = fromPromise(new Promise(resolve => setTimeout(() => resolve("Finished"), 1000)))
// fromAnimationFrames (): Signal<number>
const frames = fromAnimationFrames()
A signal that fires on every window.requestAnimationFrame. Useful in combination with sampleOn
.
A signal that emits an integer count of millisecond intervals since it was started.
// fromInterval (time: number): Signal<number>
const seconds = fromInterval(1000)
Low-level signal creation.
// create<T> (initialValue?: T): Signal<T>
const rawSignal = create()
const rawSignalWithInitialValue = create(123)
Subscribe / unsubscribe to values emitted by the signal.
// listen<T> (s: Signal<T>, f: Listener<T>): Signal<T>
// unlisten<T> (s: Signal<T>, f: Listener<T>): Signal<T>
function logger (e) { console.log(e) }
listen(clicks, logger)
unlisten(clicks, logger)
Send a value to a signal.
// send<T> (s: Signal<T>, v: T): Signal<T>
send(rawSignal, "value")
Stop a signal - no more values will be emitted.
// stop<T> (s: Signal<T>): Signal<T>
stop(rawSignal)
Map values of a signal
// map<T> (f: Mapper<T>, signal: Signal<any>): Signal<T>
const values = map(evt => evt.target.value, fromDomEvent(input, "keydown"))
Map (zip) the latest value of multiple signals
// map<T> (f: Mapper<T>, ...signals: Signal<any>[]): Signal<T>
const areas = map((x, y) => x * y, widthSignal, heightSignal)
Filter a signal, will only emit event that pass the test
// filter<T> (f: Filter<T>, s: Signal<T>): Signal<T>
const evens = filter(n => n % 2 === 0, numberSignal)
Only emit if the current value is different to the previous (as compared by ===
). Not a full deduplication.
// dropRepeats<T> (s: Signal<T>): Signal<T>
dropRepeats(numbers)
Fold a signal over an initial seed value.
// fold<T,U> (f: Folder<T,U>, seed: U, s: Signal<T>): Signal<U>
const sum = fold((a, b) => a + b, 0, numbersStream)
Merge many signals into one that emits values from all.
// merge (...signals: Signal<any>[]): Signal<any>
const events = merge(clicks, keypresses)
Take the last value of a signal when another signal emits.
// sampleOn<T,U> (s: Signal<T>, s2: Signal<U>): Signal<T>
const mousePositionsBySeconds = sampleOn(mousePosition, fromInterval(1000))
Emit an array of the last n values of a signal.
// slidingWindow<T> (length: number, s: Signal<T>): Signal<T[]>
const trail = slidingWindow(5, mousePosition)
Map values of a signal to a new signal, then flatten the results of all emitted into one signal.
// flatMap<T,U> (lift: Lifter<T,U>, s: Signal<T>): Signal<U>
const responses = flatMap(evt => fromPromise(ajaxGet("/" + evt.target.value)), keyPresses)
The same as above, but only emits values from the latest child signal.
// flatMap<T,U> (lift: Lifter<T,U>, s: Signal<T>): Signal<U>
flatMapLatest(v => fromPromise(promiseCreator(v)), valueSignal)
Debounce a signal by a millisecond interval.
// debounce<T> (s: Signal<T>, quiet: number): Signal<T>
const debouncedClicks = debounce(mouseClicks, 1000)
To put a signal in an error state, send a native Error
object to it, which will set it's value to the error, e.g:
const signal = create()
listen(signal, v => console.log(v))
send(signal, 1) // 1
send(signal, new Error("Disaster has struck")) // [Error: Disaster has struck]
So your listeners need to be handle the case that the the type of any signal value may also be an Error
.
As errors are just values, they're propagated downstream by the same mechanism:
const source = create()
const mapped = map(v => v > 1 ? new Error("I can't handle this") : v, source)
listen(mapped, v => console.log(v))
send(source, 1) // 1
send(source, 2) // [Error: I can't handle this]
Errors do not stop signals.