Giter Club home page Giter Club logo

flyd's Introduction

Flyd

The modular, KISS, functional reactive programming library for JavaScript.

Build Status Coverage Status Join the chat at https://gitter.im/paldepind/flyd

Table of contents

Introduction

Functional reactive programming is a powerful programming paradigm for expressing values that change over time. But existing libraries for JavaScript are huge, complex, have a high learning curve and aren't functional enough.

Flyd is simple and expressive. It has a minimal but powerful core on top of which new abstractions can be built modularly.

Features

Main features

  • Simple but powerful. Less is more! Flyd provides combinable observable streams as the basic building block. This minimal core is less than 200 SLOC which makes the library transparent โ€“ end users can realistically get a full understanding of how the library works.
  • More functional in style. Flyd is more functional than existing FRP libraries. Instead of methods it gives you curried functions with arguments in the order suitable for partial application. This gives more expressive power and modularity.
  • Modularity. The core of the Flyd is powerful and documented. This makes it easy for users of the library to create new FRP abstractions if existing ones do not exist. This in turn makes it viable to capture more patterns than otherwise because they can exist as separate modules. List of existing modules.

Other features

Examples

For other examples check the source code of the modules.

Tutorial

This is not general introduction to functional reactive programming. For that take a look at The introduction to Reactive Programming you've been missing and/or this Elm tutorial if you are comfortable with reading Haskell-like code.

This is not a demonstration of how you would write code with Flyd on a day to day basis. For that take a look at the examples.

This tutorial will however introduce you to the minimal but powerful core that Flyd provides and show you how it can be used to build FRP abstractions.

Creating streams

Flyd gives you streams as the building block for creating reactive dataflows. They serve the same purpose as what other FRP libraries call Signals, Observables, Properties and EventEmitters.

The function flyd.stream creates a representation of a value that changes over time. The resulting stream is a function. At first sight it works a bit like a getter-setter:

// Create a stream with initial value 5.
var number = flyd.stream(5);
// Get the current value of the stream.
console.log(number()); // logs 5
// Update the value of the stream.
console.log(number(7));
// The stream now returns the new value.
console.log(number()); // logs 7

Top level streams, that is streams without dependencies, should typically depend on the external world, like user input or fetched data.

Since streams are just functions you can easily plug them in whenever a function is expected.

var clicks = flyd.stream();
document.getElementById('button').addEventListener('click', clicks);
var messages = flyd.stream();
webSocket.onmessage = messages;

Clicks events will now flow down the clicks stream and WebSockets messages down the messages stream.

Dependent streams

Streams can depend on other streams. Use var combined = flyd.combine(combineFn, [a, b, c, ...]). The combineFn function will be called as (a, b, c, ..., self, changed) => v, where a, b, c, ... is a spread of each dependency, self is a reference to the combine stream itself, and changed is an array of streams that were atomically updated.

Flyd automatically updates the stream whenever a dependency changes. This means that the sum function below will be called whenever x and y changes. You can think of dependent stream as streams that automatically listens to or subscribes to their dependencies.

// Create two streams of numbers
var x = flyd.stream(4);
var y = flyd.stream(6);
// Create a stream that depends on the two previous streams
// and with its value given by the two added together.
var sum = flyd.combine(function(x, y) {
  return x() + y();
}, [x, y]);
// `sum` is automatically recalculated whenever the streams it depends on changes.
x(12);
console.log(sum()); // logs 18
y(8);
console.log(sum()); // logs 20

Naturally, a stream with dependencies can depend on other streams with dependencies.

// Create two streams of numbers
var x = flyd.stream(4);
var y = flyd.stream(6);
var squareX = flyd.combine(function(x) {
  return x() * x();
}, [x]);
var squareXPlusY = flyd.combine(function(y, squareX) {
  return y() + squareX();
}, [y, squareX]);
console.log(squareXPlusY()); // logs 22
x(2);
console.log(squareXPlusY()); // logs 10

The body of a dependent stream is called with the spread of: each dependency, itself, and a list of the dependencies that have changed since its last invocation (due to atomic updates several streams could have changed).

// Create two streams of numbers
var x = flyd.stream(1);
var y = flyd.stream(2);
var sum = flyd.combine(function(x, y, self, changed) {
  // The stream can read from itself
  console.log('Last sum was ' + self());
  // On the initial call no streams has changed and `changed` will be []
  changed.map(function(s) {
    var changedName = (s === y ? 'y' : 'x');
    console.log(changedName + ' changed to ' + s());
  });
  return x() + y();
}, [x, y]);

Note Returning undefined in the combineFn will not trigger an update to the stream. To trigger on undefined, update directly:

flyd.combine((_, self, changed) => { self(undefined); }, [depStream]);

Using callback APIs for asynchronous operations

Instead of returning a value a stream can update itself by calling itself. This is handy when working with APIs that takes callbacks.

var urls = flyd.stream('/something.json');
var responses = flyd.combine(function(urls, self) {
  makeRequest(urls(), self);
}, [urls]);
flyd.combine(function(responses) {
  console.log('Received response!');
  console.log(responses());
}, [responses]);

Note that the stream that logs the responses from the server should only be called after an actual response has been received (otherwise responses() would return undefined). Fortunately a stream's body will not be called before all of its declared streams has received a value (this behaviour can be circumvented with flyd.immediate).

Promises

Flyd has two helpers for dealing with promises: flyd.fromPromise and flyd.flattenPromise.

Let's say you're building a filtered list. It is important to you that the latest filter always corresponds to the latest promise and its resolution. using flyd.fromPromise guarantees the ordering, and can skip intermediate results.

const filter = flyd.stream('');
const results = filter
  .pipe(flyd.chain(
    filter => flyd.fromPromise(requestPromise(`https://example.com?q=${filter}`))
  ));

On the other hand let's say you want to sum some numbers from a service you've written. Every time someone clicks on your site you want to send a request and get back a random number to be tallied.

flyd.flattenPromise gives you the guarantee that every promise resolution will be handled, regardless of order.

const clicks = flyd.stream();
const total = clicks
  .map(getNumberAsync)
  .pipe(flyd.flattenPromise)
  .pipe(flyd.scan((acc, v)=> acc + v, 0));

Mapping over a stream

You've now seen most of the basic building block which Flyd provides. Let's see what we can do with them. Let's write a function that takes a function and a stream and returns a new stream with the function applied to every value emitted by the stream. In short, a map function.

var mapStream = function(f, s) {
  return flyd.combine(function(s) {
    return f(s());
  }, [s]);
};

We simply create a new stream dependent on the first stream. We declare the stream as a dependency so that our stream won't return values before the original stream produces its first value.

Flyd includes a similar map function as part of its core.

Scanning a stream

Lets try something else: a scan function for accumulating a stream! It could look like this:

var scanStream = function(f, acc, s) {
  return flyd.combine(function(s) {
    acc = f(acc, s());
    return acc;
  }, [s]);
};

Our scan function takes an accumulator function, an initial value and a stream. Every time the original stream emits a value we pass it to the accumulator function along with the accumulated value.

Flyd includes a scan function as part of its core.

Stream endings

When you create a stream with flyd.stream it will have an end property which is also a stream. That is an end stream:

var s = flyd.stream();
console.log(flyd.isStream(s.end)); // logs `true`

You can end a stream by pushing true into its end stream:

var s = flyd.stream();
s.end(true); // this ends `s`

When you create a dependent stream its end stream will initially depend on all the end streams of its dependencies:

var n1 = flyd.stream();
var n2 = flyd.stream();
var sum = flyd.combine(function(n1, n2) {
  return n1() + n2();
}, [n1, n2]);

sum.end now depends on n1.end and n2.end. This means that whenever one of the sums dependencies end sum will end as well.

You can change what a stream's end stream depends on with flyd.endsOn:

var number = flyd.stream(2);
var killer = flyd.stream();
var square = flyd.endsOn(flyd.merge(number.end, killer), flyd.combine(function(number) {
  return number() * number();
}, [number]));

Now square will end if either number ends or if killer emits a value.

The fact that a stream's ending is itself a stream is a very powerful concept. It means that we can use the full expressiveness of Flyd to control when a stream ends. For an example, take a look at the implementation of takeUntil.

Fin

You're done! To learn more check out the API, the examples and the source of the modules.

API

flyd.stream()

Creates a new top level stream.

Signature

a -> Stream a

Example

var n = flyd.stream(1); // Stream with initial value `1`
var s = flyd.stream(); // Stream with no initial value

flyd.combine(body, dependencies)

Creates a new dependent stream.

Signature

(...Stream * -> Stream b -> b) -> [Stream *] -> Stream b

Example

var n1 = flyd.stream(0);
var n2 = flyd.stream(0);
var max = flyd.combine(function(n1, n2, self, changed) {
  return n1() > n2() ? n1() : n2();
}, [n1, n2]);

flyd.isStream(stream)

Returns true if the supplied argument is a Flyd stream and false otherwise.

Signature

* -> Boolean

Example

var s = flyd.stream(1);
var n = 1;
flyd.isStream(s); //=> true
flyd.isStream(n); //=> false

flyd.immediate(stream)

By default the body of a dependent stream is only called when all the streams upon which it depends has a value. immediate can circumvent this behaviour. It immediately invokes the body of a dependent stream.

Signature

Stream a -> Stream a

Example

var s = flyd.stream();
var hasItems = flyd.immediate(flyd.combine(function(s) {
  return s() !== undefined && s().length > 0;
}, [s]);
console.log(hasItems()); // logs `false`. Had `immediate` not been
                         // used `hasItems()` would've returned `undefined`
s([1]);
console.log(hasItems()); // logs `true`.
s([]);
console.log(hasItems()); // logs `false`.

flyd.endsOn(endStream, s)

Changes which endsStream should trigger the ending of s.

Signature

Stream a -> Stream b -> Stream b

Example

var n = flyd.stream(1);
var killer = flyd.stream();
// `double` ends when `n` ends or when `killer` emits any value
var double = flyd.endsOn(flyd.merge(n.end, killer), flyd.combine(function(n) {
  return 2 * n();
}, [n]);

flyd.map(fn, s)

Returns a new stream consisting of every value from s passed through fn. I.e. map creates a new stream that listens to s and applies fn to every new value.

Signature

(a -> result) -> Stream a -> Stream result

Example

var numbers = flyd.stream(0);
var squaredNumbers = flyd.map(function(n) { return n*n; }, numbers);

flyd.chain(fn, s)

fn must return a stream.

fn is run every time a value is pushed into s. Returns a single stream of merged values from the created streams.

Ends when every created stream and the main stream ends

Signature

(a -> Stream b) -> Stream a -> Stream b

Example

var filter = flyd.stream('filter');
var search_results = flyd.chain(function(filter){
  return flyd.stream(getResults(filter));
}, filter);

flyd.ap(valueStream, functionStream)

Applies the value in valueStream to the function in functionStream

Signature Stream a -> Stream (a -> b) -> Stream b

Example

function add3(x) { return x + 3; }
flyd.ap(flyd.stream(5), flyd.stream(add3)) // stream(8);

while it can not seem useful immediately consider this example

var get_results = function (filter, sortProperty, sortDirection) {
  return flyd.stream(fetch(`${base_url}/search?q=${filter}&sort=${sortProperty} ${sortDirection}`))
};

// this would eventually be linked to an input field
var filter = flyd.stream('');
var sortProperty = flyd.stream('name');
var sortDirection = flyd.stream('descending');

var results = flyd.stream(flyd.curryN(3, get_results))
  .pipe(flyd.ap(filter))
  .pipe(flyd.ap(sortProperty))
  .pipe(flyd.ap(sortDirection))
  .pipe(flyd.map(function(d){ return d; }));

In the above example you have a stream of results that triggers a call for get_results every time filter, sortProperty, or sortDirection is changed.

flyd.on(fn, s)

Similar to map except that the returned stream is empty. Use on for doing side effects in reaction to stream changes. Use the returned stream only if you need to manually end it.

Signature

(a -> result) -> Stream a -> Stream undefined

Example

var numbers = flyd.stream(0);
flyd.on(function(n) { console.log('numbers changed to', n); }, numbers);

flyd.scan(fn, acc, stream)

Creates a new stream with the results of calling the function on every incoming stream with an accumulator and the incoming value.

Signature

((a, b) -> a) -> a -> Stream b -> Stream a

Example

var numbers = flyd.stream();
var sum = flyd.scan(function(sum, n) { return sum+n; }, 0, numbers);
numbers(2)(3)(5);
sum(); // 10

flyd.merge(stream1, stream2)

Creates a new stream down which all values from both stream1 and stream2 will be sent.

Signature

Stream a -> Stream a -> Stream a

Example

var btn1Clicks = flyd.stream();
button1Elm.addEventListener(btn1Clicks);
var btn2Clicks = flyd.stream();
button2Elm.addEventListener(btn2Clicks);
var allClicks = flyd.merge(btn1Clicks, btn2Clicks);

flyd.transduce(transducer, stream)

Creates a new stream resulting from applying transducer to stream.

Signature

Transducer -> Stream a -> Stream b

Example

var t = require('transducers.js');

var results = [];
var s1 = flyd.stream();
var tx = t.compose(
  t.map(function(x) { return x * 2; }),
  t.dedupe()
);
var s2 = flyd.transduce(tx, s1);
flyd.combine(function(s2) { results.push(s2()); }, [s2]);
s1(1)(1)(2)(3)(3)(3)(4);
results; // [2, 4, 6, 8]

flyd.curryN(n, fn)

Returns fn curried to n. Use this function to curry functions exposed by modules for Flyd.

Signature

Integer -> (* -> a) -> (* -> a)

Example

function add(x, y) { return x + y; };
flyd.curryN(2, add);
var add

stream()

Returns the last value of the stream.

Signature

a

Example

var names = flyd.stream('Turing');
names(); // 'Turing'

stream(val)

Pushes a value down the stream.

Signature

a -> Stream a

Example

names('Bohr');
names(); // 'Bohr'

stream.end

A stream that emits true when the stream ends. If true is pushed down the stream the parent stream ends.

stream.pipe(fn)

Returns the result of applying function fn to the stream.

Signature Called bound to Stream a: (Stream a -> Stream b) -> Stream b

Example

// map a stream
var numbers = flyd.stream(0);
var squaredNumbers = numbers
  .pipe(flyd.map(function(n) { return n*n; }));

// Chain a stream
var filter = flyd.stream('filter');
var search_results = filter
  .pipe(flyd.chain(function(filter){
    return flyd.stream(getResults(filter));
  }));

// use with a flyd module
var filter = require('flyd/module/filter');
var numbers = flyd.stream(0);
var isEven = function(x){ return x % 2 === 0; };
var evenNumbers = numbers
  .pipe(filter(isEven));

stream.map(f)

Returns a new stream identical to the original except every value will be passed through f.

Note: This function is included in order to support the fantasy land specification.

Signature

Called bound to Stream a: (a -> b) -> Stream b

Example

var numbers = flyd.stream(0);
var squaredNumbers = numbers.map(function(n) { return n*n; });

stream1.ap(stream2)

stream1 must be a stream of functions.

Returns a new stream which is the result of applying the functions from stream1 to the values in stream2.

Note: This function is included in order to support the fantasy land specification.

Signature

Called bound to Stream (a -> b): a -> Stream b

Example

var add = flyd.curryN(2, function(x, y) { return x + y; });
var numbers1 = flyd.stream();
var numbers2 = flyd.stream();
var addToNumbers1 = flyd.map(add, numbers1);
var added = addToNumbers1.ap(numbers2);

stream.of(value)

Returns a new stream with value as its initial value. It is identical to calling flyd.stream with one argument.

Signature

Called bound to Stream (a): b -> Stream b

Example

var n = flyd.stream(1);
var m = n.of(1);

Modules

If you've created a module for Flyd, open an issue or send a pull request, and it will be added to this list.

Modules listed with names in the format flyd/module/filter are builtin to the main flyd module and can be required with require('flyd/module/filter'). Other modules must be installed first with npm.

Module Description
flyd/module/filter Filter values from stream based on predicate.
flyd/module/lift Maps a function taking n parameters over n streams.
flyd/module/switchlatest Flattens a stream of streams. The result stream reflects changes from the last stream only.
flyd/module/keepwhen Keep values from one stream only when another stream is true.
flyd/module/obj Functions for working with stream in objects.
flyd/module/sampleon Samples from a stream every time an event occurs on another stream.
flyd/module/scanmerge Merge and scan several streams into one.
flyd/module/mergeall Merge a list of streams.
flyd/module/takeuntil Emit values from a stream until a second stream emits a value.
flyd/module/forwardto Create a new stream that passes all values through a function and forwards them to a target stream.
flyd/module/droprepeats Drop repeated values from a stream.
flyd-cacheUntil Cache a stream's output until triggered by another stream.
flyd-keyboard Keyboard events as streams.
flyd-glob File glob and watch for Flyd.
flyd-skip Skip function for flyd.
flyd-until only accept n event values - mirror function to flyd-skip.
flyd-bufferCount Buffers the source stream and emits all values together.
flyd-mergeAll (with high order streams) rxjs-like implementation of mergeAll for flyd.
flyd-once Only emits the first value of the source stream.
flyd-withLatestFrom When the source observable emits, the value also contains the latest value from withLatestFrom parameter stream.
flyd-zip Zip streams together into arrays of values
flyd-undo An undo/redo utility for saving and restoring state in flyd
flyd-ajax An ajax utility that returns flyd streams
flyd-xstate Integration of flyd with xstate Harel Statecharts
flyd-windowresize Get a stream for the window size
flyd-stream-querystring Manage the URL query params using flyd streams
Time related
flyd/module/every Takes a number of milliseconds t and creates a stream of the current time updated every t.
flyd/module/aftersilence Buffers values from a source stream in an array and emits it after a specified duration of silence from the source stream.
flyd/module/inlast Creates a stream that emits a list of all values from the source stream that were emitted in a specified duration.
flyd-onAnimationFrame Emits values from a source stream on successive animation frames.
flyd-timeInterval Records the time interval between consecutive values emitted from a stream.
flyd-debounceTime Like aftersilence, but only emits the latest value of the stream.
flyd-group-within buffers values within x millisecond.

Misc

The name

The name Flyd was chosen since the author of Flyd is danish and Flyd is a danish word meaning float, afloat or flow. It is furthermore short and not too bad for searching.

For most native English speakers "flyd" is impossible to pronounce like a dane would do it. The "d" is soft like "th" in "the". The "y" is a vocal sound unknown to the English language. If you're curious Google Translates listening feature provides an accurate pronounciation..

Atomic updates

Consider the following example:

var a = flyd.stream(1);
var b = flyd.combine(function(a) { return a() * 2; }, [a]);
var c = flyd.combine(function(a) { return a() + 4; }, [a]);
var d = flyd.combine(function(b, c, self, ch) {
  result.push(b() + c());
}, [b, c]);

The dependency graph looks like this.

    a
  /   \
 b     c
  \   /
    d

Now, when a value flows down a, both b and c will change because they depend on a. If you merely consider streams as being event emitters you'd expect d to be updated twice. Because a triggers b triggers d after which a also triggers c which again triggers d.

But Flyd handles such cases optimally. Since only one value entered the system d will only be updated once with the changed values of b and c.

Flyd guarantees that when a single value enters the system every stream will only be updated once, along with their dependencies in their most recent state.

This avoids superfluous updates of your streams and intermediate states when several streams change at the same time.

Flyd implements atomic updates with a O(n) topological sort where n is number of streams that directly or indirectly depends on the updated stream.

Environment support

Flyd works in all ECMAScript 5 environments. It works in older environments with polyfills for Array.prototype.filter and Array.prototype.map.

Run tests, generate documentation

To run the test, clone this repository and:

npm install
npm test

The npm test command run three tests: a eslint js style checker test, the test of the core library and the test of the modules. If you want to run only the test of the library npm run test.

The API.md file is generated using npm run docs (it assumes it has documentation installed globally: npm i -g documentation)

flyd's People

Contributors

arthurclemens avatar autosponge avatar awei01 avatar bdadam avatar bortexz avatar c-dante avatar ccorcos avatar danigb avatar dependabot[bot] avatar dmitriz avatar ericgj avatar futurist avatar gilbox avatar goldensunliu avatar ivan-kleshnin avatar jayrbolton avatar jimf avatar jwoudenberg avatar kedashoe avatar kwijibo avatar leeoniya avatar manoellobo avatar ntharim avatar paldepind avatar prayagverma avatar queckezz avatar raine avatar streetstrider avatar thomwright avatar yrns avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flyd's Issues

merge multiple

I was banging my head against a problem for a while before realizing that flyd.merge is binary. For some reason I expected it to merge all arguments.

flyd.merge(a, b, c)

I wish js would throw on these kind of errors...

I guess this is more feedback than a bug. Close if you think it's not a problem.

why mapping a stream like this?

Hey man, you seem to be pretty savvy with functional programming and I'm a newbie, so I was wondering if you could elaborate on your reasoning for this design:

var responses = flyd.stream([urls], function(resp) {
  makeRequest(urls(), resp);
});

var mapStream = function(f, s) {
  return flyd.stream([s], function() {
    return f(s());
  });
};

This just seems really odd to me, but maybe it opens up a world of opportunities, I'm just not sure... I see here how it works though...

Atomic updates

Dependency graph:

a < b
^   ^
c   |
^   |
d < result
var a = flyd.stream();
var b = flyd.stream([a], function(){return a() + 1});
var c = flyd.stream([a], function(){return a() + 100});
var d = flyd.stream([c], function(){return c() + 1000});

var result = flyd.stream([b, d], function(){
    console.log(b(), d());
    return b() + d();
});

a(1);
a(5);
a(11);

Output:

2 1101
6 1101
6 1105
12 1105
12 1111

Bower registry

Hej paldepind,

Awesome stuff! Love your minimalistic and modular approach.

Are you going to register flyd on bower any time soon?

Vh, Yuriy

Handle Error cases

Any thoughts on how to handle errors in a stream? For example, if a promise is rejected, from what i saw in code the stream will never be triggered, since the promise only invoke like this n.then(s) //line 134.

I try to improve this, but have not come up a rational solution. Should stream just end itself when it encounters an error, or should it provide a way to be listened to its errors?

switchOnNext

I must say, I like the simplicity of this library.

Some functions that I think are missing are takeUntil and switchOnNext

Suppose you are dragging an element. Its helpful to cleanup streams when they're done being used with take and takeUntil.

mouseDowns = flyd.stream()
$('body').addEventListener('mousedown', mouseDowns);
mouseDowns.map (e) ->
  elem - $(e.target)
  mouseMoves = flyd.stream()
  $('body').addEventListener('mousemove', mouseMoves);
  mouseUps = flyd.stream().take(1)
  $('body').addEventListener('mouseup', mouseUps);
  mouseMoves.takeUntil(mouseUps).map (e) ->
    elem.css('translateX', e.pageX)

Destroing on transducer reject

var click = flyd.stream();
var fiveClicks = flyd.transduce(R.take(5), click);

fiveClicks shouldn't listen more for click and will not introduce more events. So it can be destroyed with no trouble. And same for all reduced transducers

Destroy listeners

Why listeners on stream isn't destoyed on destroying stream?

function destroy(stream) {
  if (stream.listeners.length !== 0) {
    stream.listeners.forEach(destroy);
  }
  stream.deps.forEach(function(dep) { removeListener(dep.listeners, stream); });
}

I don't thought much about the consequences. But it might be very handy at first sight.

For streams that have specifical destroy logic (e.g. detach element from DOM), it's possible to call onDestroy or something like that.

function destroy(stream) {
  if (stream.listeners.length !== 0) {
    stream.listeners.forEach(destroy);
  }
  if (stream.onDestroy) {
    stream.onDestroy(stream);
  }
  stream.deps.forEach(function(dep) { removeListener(dep.listeners, stream); });
}
function renderExtension(parentElem, dep) {
    var render = flyd.stream([dep], function () {
        template(dep()).renderTo(parentElem);
    });
    render.onDestroy = function (render) {
        parentElem.removeFromDOM();
    }
}

Back-pressure

How do you handle back-pressure ?
My use case is that I need to be able to pause stream when they are not processing fast enough.

flyd.on subscriptions cannot be removed

Hello,
It's not possible to unsubscribe the .on listener on the flyd object. Shouldn't there be an .off method ? Is there a current workaround for this issue ?

flatmap issue?

Has anyone tried the flatmap module? i'm giving errors when playing with some examples

javascript
var flyd = require("flyd");
var flatmap = require("flyd-flatmap")

var main = flyd.stream()

flatmap(function(v) {
return flyd.stream(v);
}, main)

main(10)


it gives me the following error

C:\Users\Yassine\Desktop\test (2)\streams\node_modules\flyd\flyd.js:0
(function (exports, require, module, __filename, __dirname) { (function (root,
^
RangeError: Maximum call stack size exceeded


I hope i'm not doing something dumb? :)

OnEnd event

Hello @paldepind. How can I know when event is ended? For example in Rxjs exists onEnd callback.

dynamic dependencies

Hi,

I can find references in the perf folder and some of the issues on Github about the dynamic dependencies.

I tried to play with a very basic dynamic dependencies, but it doesn't work. Also, I can't find any test.

var s1 = stream();
var s2 = stream();

var sum = stream(function () {
  return s1() + s2()
})

flyd.on(console.log.bind(console, 'sum'), sum);

s1(12);
s2(1)

// expected: 13
// got:
// sum function () {
//   return s1() + s2()
// }

is this feature still supported?

Add to Most.js benchmarks

It would be nice to see flyd represented here https://github.com/cujojs/most/tree/master/test/perf

I notied that flyd relies heavily on lexical scope to access other streams. In a library context this scope chain is rather small but I'm curious/suspicious what would happen in a larger application. I am also interested to see what the impact on performance this approach will have.

How do you do error handling when using Flyd?

For example, if you create a stream from a Promise, and the promise rejects, it seems like the error is just swallowed up?

When programming with flyd, what do you do with errors from callback-based APIs?

Error handling

Again another tricky question ...

How do you handle errors in Flyd ?

split stream into two

Hi. Does flyd has an operation which takes a stream and a predicate, just like filter but returns not one stream, but two: one for values where predicate is true and other for falsy.

var input = flyd.stream()
โ€ฆ
var pred = isEven /* any predicate */
var pair = split(pred, input) /*[0] โ†’ ยซtrueyยป, [1] โ†’ ยซfalsyยป */

var evens = pair[0]
var odds  = pair[1]
/* or use destructurization */

Is this a good idea or I got something conceptually wrong? I can use just two filter's but split is better, since it automatically negate predicate for second stream, so both streams does not have rubbish values from ancestor. Is this acceptable for flyd? Other FRP?

Autocurry flyd.filter

Why aren't all the methods auto-curried?

  R.pipe(
    R.curry(flyd.map)(R.prop('outbox'))
    R.curry(flyd.filter)(nonEmpty)
    R.curry(flyd.on)(R.map(send(receive$)))
  )(model$)

Oy vey.

Program comparisons

It would be useful at least to me to see comparisons of a program such as this

https://gist.github.com/staltz/868e7e9bc2a7b8c1f754#implementing-a-who-to-follow-suggestions-box

Written in Flyd Kifir Bacon RxJS and Elm (yes, non-JavaScript`).

It may be out of scope of this project to take on such a comparison and I would actually be interested in doing this myself at some point but since your library is the new comer to the scene I'd like to see your thoughts/effort here. Kifir in particular was among other things designed for performance https://pozadi.github.io/kefir with some demos showing that http://jsfiddle.net/jL1nm3c3/2.

Thanks for adding another FRP option in JS.

Handling out of order responses from Promises

function updateStreamValue(s, n) {
  if (n !== undefined && n !== null && isFunction(n.then)) {
    n.then(s); // here??
    return;
  }
  ...
}

If the promises are resolved in an arbitrary order (eg. the second promise resolve before the first) then the stream wll be updated with an outdated value

Library combine inconsistencies

While doing the refactor from #71 I noticed that both scan and merge filter out undefined values.

For dependent streams, the combinator returning undefined won't update the stream:

var returnVal = s.fn(s, s.depsChanged);
if (returnVal !== undefined) {
  s(returnVal);
}

(https://github.com/paldepind/flyd/blob/master/lib/index.js#L63)

Scan and merge (and any dependent streams that return a value instead of using self) will filter undefined.
(https://github.com/paldepind/flyd/blob/master/lib/index.js#L23)

I think internal functions should preserve the undefined event and pass it along to merge and scan.

(Possible) Unused variable in flyd.js, L163

Hi all,
Thank you for making such a great library! It's so small, yet so capable.
I came across the code for createStream() in flyd.js, line 163, and found that i and list is not uses by the function, nor are they bound to this. I'm quite new to js, and frequently get confused with this, so maybe I got it wrong.
Thank you in advance.

curryN code duplication

I peaked into the source of flyd today. One thing I noticed that there is a curryN implementation which seems to be exactly the same as the one from ramda. If one uses ramda already, there is quite some code duplication.

Why did you choose to copy it into source instead of requiring the function? If it's an altered version maybe there's potential to merge it back into ramda?

var curryN = require('ramda/src/curryN')

Also I think the boilerplate in the source belongs to a build step:

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    define([], factory); // AMD. Register as an anonymous module.
  } else if (typeof exports === 'object') {
    module.exports = factory(); // NodeJS
  } else { // Browser globals (root is window)
    root.flyd = factory();
  }
}(this, function () {

We could use something like browserify to make a final bundle for those who aren't using require() on the client and distribute that:

browserify lib/index.js --standalone > flyd.js

Just my 2ยข! All in all a seriously awesome library :) I'm also looking forward to play around with snappdom ;)

Naming conventions

Hi there!
I really like your approach to FRP. But why the hell dont you use standard method names? afterSilence is debounce for sure I suppose.

Streams & ADTs

Hi @paldepind, this is not exactly an issue, it's just i'm always curious about new reactive streams implementations and ideas, so feel free to close it whenever you like.

i did myself some experiments on this field, it'd be great if you could look at it (if you've got some time) and tell me what u think. This is just an experimental concept, the idea is to uses Algebraic Data Types and Promises to encapsulate the async cases.

IMO flyd seems to offer more flexibility than other libs (Bacon, Rx) when it comes to defining new operations (the self parameter on the stream callback is a simple yet powerful idea). And i like the idea of a stream being plugged like a normal callback in multiples event sources.

What's interesting is the idea of the stream end being itself a stream, but i find it quite natural from a pure functional view, the ADT definition of a list is List a = Empty | Cons a (List a), in other terms the end of list (Empty) is a list itself. So it's also natural to think of the stream end as a special case of streams (i did the same thing in my lib).

Are there any plans to support dynamic dependencies

Rx module

Hi @paldepind I think than would be nice provide a rx module with the most used Rx functions, they're just only a bunch of basic functions

http://rxmarbles.com

could you give me an implementation of debounce function?..I did my own but I consider the result pretty poor, would be nice watch the correct implementation.

thanks!

differences with bacon and kefir: isn't it inspired in CES?

Hi, kefir and bacon seems to be inspired in Compositional event system, I'm more familiar with kefir and for instance, if you need get a stream value in kefir, you need generate an event, instead in flyd you only do

someStream()

you can't get the value of one stream directly except calling some event which generate other stream and use composition of streams, also all the functions works returning new streams (similar to flyd) but for instance in kefir you can't write side effect or not transparent functions
this little change is a big difference in the way how you build your algorithms, for instance, in flyd I can write this

flyd.stream([sa],()=>{sb(sa() ....)}) 

I admit than it's pretty cool although a bit nasty: the function is not even transparent where generally in CES I can't write this kind of code...

at the end I don't know if it's a bit weak or if It's other kind of FRP than I dont know (I've never used languages like elm or some libs from haskell) is flyd inspired in elm or it's a more flexible and not so formal CES?? I've admit than right now I found this pretty cool but I also feel than it's not so frp formal

in kefir when I need generate side effect I use onValue(stream,stream->void) where flyd doesnt provides this function but it's not so necessary because you can use stream or even map, flyd doesnt care about it although use map would be very dirty...

how is flyd compared to elm and other FRP like RX or kefir?...is it other kind of frp or you build it thinking in a more flexible approach?

it's interesting because more formal CES sometimes can be a bit pita and flyd seems avoid these situations with a more flexible philosophy...

Stream callback needs to provide the dependant streams in arguments

Consider your example:

var x = flyd.stream(4);
var y = flyd.stream(6);
var sum = flyd.stream([x, y], function() {
  return x() + y();
});

Now how would you break this in modules?

var callback = require('./callback');

var x = flyd.stream(4);
var y = flyd.stream(6);

var sum = flyd.stream([x, y], callback);

// callback.js

module.exports = function(self, changed) {
    // how would you get x and y here?
  return x() + y();
}

Example secret combination bug

After getting the combination correct, clicksInLast5s still fires 6 more times each time causing the lift's function to display "You're not fast enough, try again!".

liftAllObj

I have a suggestion for lifting an object of streams.

liftAllObj = (signals) ->
  labeled = ([name, stream]) ->
    flyd.map(
      (value) -> {"#{name}": value}
      stream
    )

  streams = R.pipe(
    R.toPairs
    R.map(labeled)
  )(signals)

  reducer = (acc, next) ->
    flyd.lift(R.merge, acc, next)

  R.reduce(reducer, R.head(streams), R.tail(streams))


obj = 
  x: flyd.stream(1)
  y: flyd.stream(2)
  z: flyd.stream(2)

s = liftAllObj(obj)

flyd.on(console.log.bind(console), s)

obj.x(10)
obj.y(20)
obj.z(30)

Dragging an element, infinite loop

I made a pen demonstrating the problem:

http://codepen.io/ccorcos/pen/OVpLPv?editors=101

I'm not sure why I'm getting an infinite loop here. Note that I've flipped the order of takeUntil. It seems to make more sense to me. So you can say takeUntilEnd = takeUntil(end) if its properly curried.

Anyways, I have no idea why theres this infinite loop. Theres no problem when I remove the inner map...

var start = flyd.stream();
var move = flyd.stream();
var end = flyd.stream();

var $elem = $('.drag');
$elem.on('mousedown', R.pipe(R.prop('pageX'), start));
$elem.on('mousemove', R.pipe(R.prop('pageX'), move));
$elem.on('mouseup', R.pipe(R.prop('pageX'), end));

function takeUntil(term, src) {
  return flyd.endsOn(flyd.merge(term, src.end), flyd.stream([src], function(self) {
    self(src());
  }));
};

flyd.map(function(x) {
  console.log("start", x)
  var offset = $elem.position().left - x;
  flyd.map(function(x) {
    console.log("move", x)
    $elem.css('translateX', x-offset)
  }, takeUntil(end, move));
}, start);

Small bugs

Really like your approach to FRP, small and easy to use ๐Ÿ‘

Few questions/findings:
the example below is from your readme, it seems to be missing a ); after changed.map
also it seems like changed is undefined on the initial call rather than [] so calling map on undefined fails
and i think s() should be s otherwise throws a error saying s is not a function...

var x = flyd.stream(1);
var y = flyd.stream(2);
var sum = flyd.stream([x, y], function(sum, changed) {
  // The stream can read from itself
  console.log('Last sum was ' + sum());
  // On the initial call no streams has changed and `changed` will be []
  changed.map(function(s) {
    var changedName = (s === y ? 'y' : 'x');
    console.log(changedName + ' changed to ' + s());
  }
  return x() + y();
});

Also I did

flyd.isStream(x); // returns true
x.end(true); // throws TypeError

however it x doesn't seem to have a property end on it so i cannot call it?

combineLatest

http://reactivex.io/documentation/operators/combinelatest.html

This is also a helpful function. It can often be used with switchOnNext as well.

searchStream = flyd.stream('')

searchStream.switchMapCombineLatest(function(query) {
  searchResultsStream(query)
}).map(function(query, results) {
  // now I have the results AND the query!
})

Now switchMapCombineLatest is kind of a long name here. RxJS uses combineLatest(stream1, stream2) but I find it most useful inline like the example I gave you.

Use ajax with scanMerge

Hello, How can I use ajax response with scanMerge? For example:

const fromPromise = (promise) => {
    var s = flyd.stream();

    promise.then(v => s(v));

    return flyd.immediate(flyd.stream([s], function () {
        return s();
    }));
};

const ajaxStream = (key) => fromPromise(emulateAjaxWithDelay(key));

const personStream = flatMap(person => {
    return scanMerge(
        [
            [changeFirstName, (person, firstName) => {
                console.log(person);
                console.log(firstName);
                return person.set('firstName', firstName);
            }],
            [changeLastName, (person, lastName) => person.set('lastName', lastName)],
            [changeCountry, (person, country) => person.setIn(['country', 'name'], country)],
            [addFriend, (person, friend) => person.set('friends', person.get('friends').push(friend))],
            [removeFriend, (person, friendIndex) => person.set('friends', person.get('friends').splice(friendIndex, 1))],
            [save, (person, _) => saveToLocalStorage('person', person)],
            [undo, (person, historyPerson) => historyPerson],
            [redo, (person, futurePerson) => futurePerson]
        ],
        person
    )
}, ajaxStream('person'));

flyd.on(p => console.log(p), personStream);

But this code doesn't work. Person has value in flatMap but scanMerge doesn't emit value. My RxJS version here https://github.com/xgrommx/react-rx-flux/blob/master/src/store.js#L24

Duplicate values

Consider this scenario, we want to send values to s1 on s2's changes:

  const s1 = stream();
  const s2 = stream();

  on(s1, s2);
  on(s1, s2.map( x => x * 2));

  s1.map(x => console.log(x));

  s2(1);

This logs:

1
1

Isn't it supposed to log 1 2 instead?

1
2

async animations using streams

I'm trying to figure out how to do something relatively simple, but using streams.

I have a stream of actions that trigger page transitions, called routeStream. When an animation is triggered, no other animations can be triggered while the animation is running. If any actions are emitted from routeStream while the animation is running, then when the animation stops, only the most recent action will be animated.

Without really using streams, I can do something like this:

next = null
busy = false
done = function() {
  if (next) {
    n = next
    next = null
    n(done)
  } else {
    busy = false
  }
}
animateLatest = function(f) {
  if (busy) {
    next = f
  } else {
    busy = true
    f(done)
  }
}

flyd.map(function(route) {
  animateLatest(animations[route])
}, routeStream)

It seems to me like I should be able to do all of this with streams, but I'm really struggling for some reason...

Using flyd.on returns undefined instead of a stream

I'm using flyd along with React and I'm trying to get rid of one of its warning. Basically, I found out that I may have some kind of race condition and therefore decided to manually clean up all streams when a component unmounts.

Long story short: flyd.on() returns undefined instead of a stream that I could then close. Did I miss something or is that a real issue?

var s1 = f.stream()
var s2 = f.on(function(x) {console.log(x)}, s1)
s1
stream(undefined)
s2
undefined
s2.end
VM26253:1 Uncaught TypeError: Cannot read property 'end' of undefined(โ€ฆ)

Thanks for the library, I've been playing with the RxJS and likes, and this one is much easier to get into!

Handling state

If you don't want to answer this you don't have to but I am reaching out to you because flyd is the simplest, sanest FRP library I have encountered. I am a beginner however. I am building a native app framework for my own use on personal projects. I have taken techniques from the virtual dom/React community and built some powerful and simple rendering pipelines from a small core that is render agnostic, it just emits ordered hierarchical key value pairs that represent the UI and the renderers consume batches of this data and render iOS, Mac or web UI. I built the core from scratch to be very simple and easy to inspect. All state and input are saved in a few global but safe hashmaps that are persisted to SQLite. I call this core a playground and can extend it with pure functions I call plays that act on state and input to produce more key value pairs that can represent other plays.

I now want to make my core more FRP like. Instead of thinking of the data as static, I want to think of it in streams. My question for you is how to do this while still maintaining an easy to inspect, freeze, restore, debug concept of state. Are there any state libraries or techniques that go really well with flyd. I am considering using flyd to implement the core playground. The playground would be super easy to convert to JS as it uses very basic data structures and has only a loose dependency on SQLite as a data sink.

Any thoughts. I know it's hard to understand and feel free to ignore if this is too vague. Again I need a rock solid and easy to verify state mechanism with a FRP core that is very practical.

Laziness

Implementing laziness would be quite easy. But it definitely has some negatives impacts on user friendliness.

Please make it without impact on possibility to get value of stream (by default, but it's ok to provide different methods with laziness enabled), and so it'll not create problems with stateful streams.

In theory it looks like not so big issue. But when I tried to build idiomatic FRP code with kefir, it was extremely irritatingly. The main problems was with getting current value, and stateful streams. Also there was problems with atomic updates. Bacon sample have broken when I tried to juggle with its restrictions. And it was issue of bacon because same code on kefir works good, although code looks horrible.

But when I tried flyd it was so simple to build whatever I want. I stopped worrying about laziness, many restrictions, and started to think more about logic. Most of streams was static, so garbaging it wasn't an issue. But in some places where I create streams in loops it's possible to use end (or use helper that ends temporary stream when needed as in discussion of this issue).

Also we should have choice. Flyd interestingly different from other FRP libs. I think many will appreciate advantages of flyd more than its disadvantages.

Switchlatest misses event emitted before

Example to illustrate the problem:

var flyd = require('flyd');
var switchLatest = require('flyd/module/switchlatest');
var flatMap = require('flyd/module/flatmap');


const s1 = flyd.stream();
s1(1);

const s2 = switchLatest(
    flyd.map(v => flyd.stream('switchlatest: ' + v), s1)
).map(console.log.bind(console));

const s3 = flatMap(v => flyd.stream('flatmap: ' + v), s1
).map(console.log.bind(console));

s1(2);

// print:
//
// flatmap 1
// flatmap 2
// switchlatest 2

SwitchLatest misses the event emitted before it's hooked, while flatMap captured both 2 events. This causes problem in my use case:

const refresh = params => (
    params ? flyd.map(() => params, every(3000)) : flyd.stream();
);

switchLatest(toggle.map(refresh)).map(handle);

The resulting stream periodically does something with params, and does nothing if (be turned off) if falsy values are passed in. However, only after 3 seconds handle will be called for the first time, which is not the intended behavior.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.