baconjs / bacon.js

Functional reactive programming library for TypeScript and JavaScript

Home Page:https://baconjs.github.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Why won't `property.sampledBy()` wait for `property` to receive its first value?

timmolendijk opened this issue · comments

I would have expected property.sampledBy() to wait for property to hold its first value, similar to how Bacon.when() waits for each of its inputs to have a value before invoking any of its join functions.

As far as I can see, this is not what currently happens:

Compare:

// logs "sampled from property: property value" as expected
Bacon.fromCallback(function (cb) {
  cb("property value");
}).toProperty().
  sampledBy(Bacon.once(true)).
  onValue(function (v) {
    console.log("sampled from property:", v);
  });

… to:

// does not log anything
Bacon.fromCallback(function (cb) {
  // deferring callback makes the sampler do nothing at all
  setTimeout(function () {
    cb("property value");
  });
}).toProperty().
  sampledBy(Bacon.once(true)).
  onValue(function (v) {
    console.log("sampled from property:", v);
  });

Which brings me to the question, if what I am trying to do in this last example is not supported, how am I supposed to implement that logic?

Since the following does not work:

var property = Bacon.fromCallback(function (cb) {
  // deferring callback makes the sampler do nothing at all
  setTimeout(function () {
    cb("property value");
  });
}).toProperty();

var stream = Bacon.once(true);

stream.
  map(property).
  onValue(function (v) {
    console.log("sampled from property:", v);
  });

… the only alternative I can think of is:

var property = Bacon.fromCallback(function (cb) {
  // deferring callback makes the sampler do nothing at all
  setTimeout(function () {
    cb("property value");
  });
}).toProperty();

var stream = Bacon.once(true);

stream.
  flatMapLatest(function () {
    // `take(1)` is technically not necessary with our current callback-based property, but
    // imagine that we are dealing with a property that does not complete.
    return property.take(1);
  }).
  onValue(function (v) {
    console.log("sampled from property:", v);
  });

Unfortunately, as described in #537 this will not work either.

I'm stuck guys, what am I missing that this seemingly simple logic turns out being such a brainf#ck??

Well, sampledBy picks a value from the Property when an event in the "sampler" stream occurs, as specified. I guess you want it to produce a value on the first property value in case the value was missing on the first sampling event.

I picked found this snippet from the Bacon.js Google Group:

Bacon.Property.prototype.sampledByWithDefault = function(sampler){
      return this.sampledBy(sampler.merge(this.take(1).changes().skipUntil(sampler)));
};

I guess this will solve your problem? If we came up with a suitable name and test and docs, we might add this to the library.

Thanks, that works!

In the meantime I found out that Bacon.when([property, …], …) actually behaves exactly like property.sampledBy(…) in that it doesn't do anything if property does not have a value at "sample time". Which makes it a predictable behavior in my view. (I was confused by Bacon.combineWith(), which does wait for properties to hold a value.)

I'm not convinced adding a specialised method is a good idea, as we would have to come up with a similar one to accompany map(property) and things may end up more confusing instead of less.

On second thought, it may be useful to have this in the core library, as I find I need it pretty much everywhere that I use .map(property) or property.sampledBy().

Perhaps we could go for property.syncSampledBy() and .syncMap(property)…?

Or property.sampledByAll() and .mapAll(property)…?

Or property.onReadySampledBy() and .mapOnReady(property)…?

Meh, all rather ugly and potentially confusing quite frankly.

Maybe, just maybe, wouldn't it be better to change the current behavior of .map(property) and property.sampledBy()? I mean, when is it ever useful to have an event "map to nothing" or "take a sample of nothingness"? Is there any reason to suspect that there are many use cases in which this aspect of its behavior is actually specifically desired or depended on?

What would it take to consider a (breaking) change such as this one?