Removing Kefir.emitter()
See original GitHub issueTLDR; (For people who came here from “How do I create Emitter/Subject/Bus in Kefir?” questions)
Kefir once had Kefir.emitter() that had both Stream and Emitter interfaces. Then it was removed, because:
- We prefer to do things in declarative/functional style rather than imperative in Kefir and FRP
Kefir.emitter()
is an imperative pattern- In 90% of cases
Kefir.emitter()
can be replaced with some declarative/functional style pattern (you can find some examples bellow in this thread) - Lack of
Kefir.emitter()
should inspire people to find that better patterns - In remaining 10% cases one can extract emitter from
Kefir.stream(emitter => ...)
(see https://github.com/rpominov/kefir/issues/88#issuecomment-92512136)
Also note that Kefir.fromBinder()
mentioned often bellow is renamed to Kefir.stream()
now.
If you have more questions, please continue discussion here rather that where from you have been redirected to this thread. So we have all answers in one place.
Below goes original issue body:
Kefir.emitter()
looks like the most basic method for creating general purpose streams, but I think it actually an antipattern. Beginners tend to overuse it, but people who get FRP right, use it very rarely or even never.
I think we should remove (deprecate) Kefir.emitter()
, and advertise Kefir.fromBinder()
as the main method for creating general purpose streams. And we should probably rename Kefir.fromBinder()
to something looking less scary and more like a main method for creating streams — Kefir.stream()
or something. Also Kefir.bus()
should be removed, as it mostly same as Kefir.emitter()
.
This proposition also aligns well with idea of minimizing API surface area https://github.com/pozadi/kefir/issues/71
Here is the issues one can run into when using Kefir.emitter()
:
- Emitting before anyone listening:
var emitter = Kefir.emitter();
emitter.emit(1);
// The callback obviously won't be called with `1`, but sometimes it
// not so obvious for some people.
emitter.onValue(function(x) {
console.log(x);
});
// Right way to achieve this
var stream = Kefir.fromBinder(function(emitter) {
emitter.emit(1);
});
// will log `1`
stream.onValue(function(x) {
console.log(x);
});
- Resources are not managed correctly:
// bad
function getStream() {
var emitter = Kefir.emitter();
// subscriber will never be removed
target.addEventListener('click', function(event) {
emitter.emit(event);
});
}
// good
function getStream() {
return Kefir.fromBinder(function(emitter) {
target.addEventListener('click', emitter.emit);
return function() {
// free resources
target.removeEventListener('click', emitter.emit);
}
});
}
-
A property not getting values from an emitter while not active (see http://pozadi.github.io/kefir/#active-state)
-
Methods for emitting events are exposed for all users of emitter-srteam:
var stream = getStream(); // using function from example above
// no one stops me to doing:
stream.emit(1);
And I guess there is more.
Issue Analytics
- State:
- Created 8 years ago
- Comments:32 (20 by maintainers)
Top GitHub Comments
I came across this issue while working on a Typescript code base and thought I’d drop in a type-safe implementation for future google-users. It’s just a slightly tweaked version of the JS version above.
I needed this because I’m introducing FRP into a codebase that didn’t initially have it, and I don’t want to have to refactor the whole thing at once.
@rpominov Yea I’m not sure if this helps. By using a property, you trade “missing events” to “getting the wrong event”…which is just as potentially bad and hard to debug.
Thinking about this a little more, is the real issue that FRP isn’t designed for dynamically created listeners? It seems that the correct pattern should be “setup your listeners, then do stuff”. At least when analyzing the first and second gotchas (at the start of this thread), violating this pattern causes the issues. The same is true even if we use a pool (as shown above).
On a related note, I just finished reading this reddit and that guy seconded my notion that Buses were invented for exactly a use case like ReactJS.