Почему flatMap и fromEvent работают только при возврате во время первой подписки, как в примере?

Как показано ниже, почему пример 1 терпит неудачу, когда мы возвращаем div, а затем отдельно возвращаем новое наблюдаемое по клику flatMap в click$ ?

Пример 2 работает нормально. JSBin ниже, чтобы попробовать

Кто-нибудь может объяснить, почему это происходит? Насколько я понимаю, flatMap расширяет Observable. http://jsbin.com/sowodi/3/edit?js,console,output

// Example 1
() => {
  let stream = Rx.Observable.fromArray([1, 2, 3]);

  let div$ = stream.map(i => {
    let div = document.createElement('div');
    div.innerHTML = `NOT WORKING DIV ${i}`;
    return div;
  })

  div$.subscribe(div => {
    document.querySelector('body').appendChild(div);
  })

  let click$ = div$.flatMap(
    div => Rx.Observable.fromEvent(div, 'click')
  );

  click$.subscribe(click => console.log('click'));
}();


// Example 2
() => {
  let stream = Rx.Observable.fromArray([4, 5, 6]);

  let click$ = stream.flatMap(i => {
    let div = document.createElement('div');
    div.innerHTML = `DIV ${i}`;
    document.querySelector('body').appendChild(div);

    return Rx.Observable.fromEvent(div, 'click');
  })

  click$.subscribe(click => console.log('click'));
}();
<!DOCTYPE html>
<html>

<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.6/rx.all.js"></script>
  <meta charset="utf-8">
  <title>JS Bin</title>
</head>

<body>
  <div id="app">
    THE APP ID
  </div>
</body>

</html>


person FeliciousX    schedule 05.02.2016    source источник
comment
Я не думаю, что RxJs гарантирует, что подписка на добавление будет запущена до обратного вызова flatMap в этом сценарии. Вы пытались добавить ведение журнала к обоим для подтверждения?   -  person Richard Szalay    schedule 05.02.2016
comment
Я попробовал и обнаружил, что добавление всегда запускается перед плоским отображением. Я тоже думаю, что порядок не имеет значения? Поскольку вы можете перехватить событие onClick перед добавлением в DOM   -  person FeliciousX    schedule 05.02.2016


Ответы (1)


Это потому, что в вашем первом примере вы создаете два набора div. Вы сделали эквивалент этого:

  Rx.Observable.fromArray([1, 2, 3])
    .map(i => {
      let div = document.createElement('div');
      div.innerHTML = `NOT WORKING DIV ${i}`;
      return div;
    }).subscribe(div => {
      document.querySelector('body').appendChild(div);
    })

  Rx.Observable.fromArray([1, 2, 3])
    .map(i => {
      let div = document.createElement('div');
      div.innerHTML = `NOT WORKING DIV ${i}`;
      return div;
  }).flatMap(
      div => Rx.Observable.fromEvent(div, 'click')
  ).subscribe(click => console.log('click'));

Причина в том, что fromArray создает ссылку холодный Observable означает, что каждая подписка будет создавать новый независимый поток, который не повлияет на другие созданные вами потоки.

Есть два способа исправить это: 1) который вы уже обнаружили, — это создать только один поток для размещения всей вашей логики или 2) в качестве альтернативы вы можете преобразовать результат из div$ в горячий Observable, чтобы все подписки фактически поделиться одним и тем же потоком. Это будет иметь свой собственный набор проблем, а именно то, что теперь вы можете пропустить сообщения, если не будете осторожны, но для рефакторинга вашего первого случая это будет выглядеть так:

  let stream = Rx.Observable.fromArray([1, 2, 3]);

  let div$ = stream.map(i => {
    let div = document.createElement('div');
    div.innerHTML = `NOT WORKING DIV ${i}`;
    return div;
  })
  //Make div$ into a connectable Observable so the subscriptions will
  //share the underlying Observable
  .publish()

  div$.subscribe(div => {
    document.querySelector('body').appendChild(div);
  })

  let click$ = div$.flatMap(
    div => Rx.Observable.fromEvent(div, 'click')
  );

  click$.subscribe(click => console.log('click'));

  //Nothing happens until you call connect which subscribes to the underlying
  //Observable
  div$.connect();
person paulpdaniels    schedule 05.02.2016