Reactive-Extensions / RxJS

The Reactive Extensions for JavaScript
http://reactivex.io
Other
19.49k stars 2.1k forks source link

An alternative for Bacon.update or Bacon.when #463

Closed xgrommx closed 9 years ago

xgrommx commented 9 years ago

Hello @mattpodwysocki,

What is alternative in Rx for Bacon.update or Bacon.when? I guess that it's very helpful operator. Small example for a simple counter http://jsbin.com/jenanuceme/1/edit?html,js,output

xgrommx commented 9 years ago

Could someone help me?

mattpodwysocki commented 9 years ago

@xgrommx we already have an equivalent to Bacon.when with Rx.Observable.when

Such code could be written as such without using jQuery:

function $$(elementString) {
  return document.querySelector(elementString);
}

function click(element) {
  return Rx.Observable.fromEvent(element, 'click');
}

function onLoad () {

  var $display = $$('#display');

  function getValue() {
    return +$$('#display').textContent;
  }

  var $plusClick = click($$('#plus')).map(getValue),
      $minusClick = click($$('#minus')).map(getValue),
      $resetClick = click($$('#reset'));

  var $updates = Rx.Observable.when(
    $plusClick.thenDo(function (x) { return x + 1; }),
    $minusClick.thenDo(function (x) { return x - 1; }),
    $resetClick.thenDo(function () { return 0; })
  ).startWith(0);

  $updates.subscribe(function (x) {
    $display.textContent = x;
  });
}
window.onload = onLoad;
xgrommx commented 9 years ago

As I understood, if I will use startWith I will have Bacon.update. Also I have one small question, if I want use composition of two or many observables in condition like in Bacon.update https://github.com/baconjs/bacon.js/#join-patterns-and-baconbus how can I solve it? For example this http://en.wikipedia.org/wiki/Dining_philosophers_problem

xgrommx commented 9 years ago

Sorry, I found that Rx already have an operator and. I think this operator is just right.

xgrommx commented 9 years ago

@mattpodwysocki I think this is good example for demonstration how user could use a when operator:

var chopsticks = [new Rx.Subject(), new Rx.Subject(), new Rx.Subject()];

var hungry = [new Rx.Subject(), new Rx.Subject(), new Rx.Subject()];

var eat = i => {
  return () => {
    setTimeout(() => {
      console.log('Done');
      chopsticks[i].onNext({});
      chopsticks[(i+1) % 3].onNext({});
    }, 1000);
    return 'philosopher ' + i + ' eating';
  };
};

var dining = Rx.Observable.when(
  hungry[0].and(chopsticks[0]).and(chopsticks[1]).thenDo(eat(0)),
  hungry[1].and(chopsticks[1]).and(chopsticks[2]).thenDo(eat(1)),
  hungry[2].and(chopsticks[2]).and(chopsticks[0]).thenDo(eat(2))
);

dining.subscribe(console.log.bind(console));

chopsticks[0].onNext({}); chopsticks[1].onNext({}); chopsticks[2].onNext({});

for (var i = 0; i < 3; i++) {
  hungry[0].onNext({}); hungry[1].onNext({}); hungry[2].onNext({});
}

http://jsbin.com/xocalu/2/edit?js,console

mattpodwysocki commented 9 years ago

@xgrommx that's great! If you'd like to turn it into an official example with a web page, we'd be more than happy to accept it!

xgrommx commented 9 years ago

@mattpodwysocki Oh sure I can added it to the official documentation and to my rx book.

gyzerok commented 9 years ago

@mattpodwysocki thank you for answering this question, but let me ask you one more to make this topic complete.

How do i get prev state of .when stream inside its functions? In other words how to transpile following example to Rx?

var foo = Bacon.update([],
    [bar], function (prevFoo, barValue) {
           return prevFoo.concat(barValue);
    }
    [baz], function (prevFoo, bazValue) {
           // remove bazValue from prevFoo here
           return prevFooWithoutBazValue;
    }
);
urmastalimaa commented 9 years ago

I'm not very familiar with the Bacon example, but your best bets are

bufferWithCount(2)

or possibly

reduce
xgrommx commented 9 years ago

@gyzerok If you look inside update method from Bacon you can found scan. I just try reproduce this operator in Rx and I use it so:

let PersonStore = Rx.Observable.when(
    Actions.changeFirstName.debounce(2000).thenDo(firstName => person => person.set('firstName', firstName)),
    Actions.changeLastName.thenDo(lastName => person => person.set('lastName', lastName)),
    Actions.changeCountry.thenDo(country => person => person.setIn(['country', 'name'], country)),
    Actions.addFriend.thenDo(friend => person => person.set('friends', person.get('friends').push(friend))),
    Actions.save.debounce(2000).thenDo(_ => person => {
        console.log(person.toJS());
        return person;
    })
).scan(I.fromJS(defaultPerson), (person, action) => action(person)).publishValue(I.fromJS(defaultPerson)).refCount();

We can pass callback function to scan method. publishValue(I.fromJS(defaultPerson)).refCount() is alternative for Bacon.toProperty(I.fromJS(defaultPerson))

gyzerok commented 9 years ago

@xgrommx Thank you for your answer! May I ask you to convert my particular example from Bacon to Rx? So I can get it right.

xgrommx commented 9 years ago

@gyzerok Welcome! As you can see person is previous value, action(person) is new value. Maybe in the future I could find more better example but now I use it. If you have some questions I'll try answer =) By the way the method update exists only in Bacon and make this library unique.

gyzerok commented 9 years ago

@xgrommx I cannot find any information about thenDo method in the docs. Is it deprecated?

And I cant understand where in the following code person comes from

Actions.changeFirstName.debounce(2000).thenDo(firstName => person => person.set('firstName', firstName))
gyzerok commented 9 years ago

@xgrommx I guess I get general solution for this. Mb it can be better?

function update(initial, ...patterns) {
  const streams = patterns.filter((_, i) => i % 2 === 0);
  const callbacks = patterns.filter((_, i) => i % 2 !== 0);
  const pairs = streams.map((_, i) =>
    [streams[i], callbacks[i]]
  );
  return Rx.Observable.when.apply(this, pairs.map(p =>
    p[0].thenDo(data => prev => p[1](prev, data))
  )).scan(initial, (prev, f) => f(prev));
}

Test code:

let a = new Rx.Subject();
let b = new Rx.Subject();

let test = update(0,
  a, (prev, data) => prev + data,
  b, (prev, data) => prev - data
);

test.subscribe(console.log.bind(console));

a.onNext(1);
a.onNext(5);
b.onNext(3);
b.onNext(2);
xgrommx commented 9 years ago

@gyzerok You can look on this http://xgrommx.github.io/rx-book/content/observable/observable_instance_methods/thendo.html

xgrommx commented 9 years ago

@gyzerok This is my alternative for Bacon.update https://github.com/xgrommx/react-rx-flux/blob/master/src/store.js#L7. Can use array of observables as in Bacon.update.