Как соотноситься с источниками?

У меня есть следующий код:

const Cycle = require('@cycle/core');
const {Observable} = require('rx');

function main(sources) {
    const B$ = sources.driverA
        .concat(Observable.just('b'))
        .concat(Observable.just('c'));

    const C$ = sources.driverB.map(x => x.toUpperCase());

    return {
        driverA: Observable.just('a'),
        driverB: B$,
        driverC: C$
    }
}

Cycle.run(main, {
    driverA: (A$) => A$,
    driverB: (B$) => B$,
    driverC: msg$ => { msg$.subscribe(msg => console.log(msg)) }
});

Я ожидаю получить три строки на консоли: A, B и C, но я получаю только последнюю строку. Похоже, что driverC получает только последнее сообщение, хотя B$.tap(console.log) выводит все три ("a", "b", "c");

Чем объясняется такое поведение и как я могу передать все три сообщения в driverC?

Версии:

  • @ Цикл / ядро ​​@ 6.0.3
  • rx@4.1.0

2 ответа

Решение

Объяснение поведения

Ну, на самом деле это не легко объяснить. Это связано с тем, как cycle.run провода свой цикл. Следующий код запускается в trycicle:

const Cycle = require('@cycle/core');
const {Observable} = require('rx');

function main(sources) {
    const B$ = sources.driverA
        .concat(Observable.just('b'))
        .concat(Observable.just('c'))
        .concat(Observable.just('d'));

    const C$ = sources.driverB.map(x => x.toUpperCase());

    return {
        driverA: Observable.just('a'),
        driverB: B$,
        driverC: C$
    }
}

Cycle.run(main, {
    driverA: (A$) => A$.tap(msg => console.log(msg)),
    driverB: (B$) => B$.tap(msg => console.log(msg)),
    driverC: msg$ => { msg$.subscribe(msg => console.log(msg)) }
});

и только отображает a d D, Так что это на самом деле последняя буква, которая показана.

Теперь, если вы запустите это:

const Cycle = require('@cycle/core');
const {Observable} = require('rx');

function main(sources) {
    const B$ = sources.driverA
        .concat(Observable.just('b').delay(1))
        .concat(Observable.just('c'))
        .concat(Observable.just('d'));

    const C$ = sources.driverB.map(x => x.toUpperCase());

    return {
        driverA: Observable.just('a'),
        driverB: B$,
        driverC: C$
    }
}

Cycle.run(main, {
    driverA: (A$) => A$.tap(msg => console.log(msg)),
    driverB: (B$) => B$.tap(msg => console.log(msg)),
    driverC: msg$ => { msg$.subscribe(msg => console.log(msg)) }
});

ты получаешь a a A b B c C d D чего ты и ожидал

Что происходит то run подключайте драйверы к источникам через предметы и делайте это по порядку. Какой заказ? Порядок перечисления свойств в var x in obj, которая не указана и, следовательно, не может зависеть от - может зависеть от браузера (ср. Вводит ли ES6 четко определенный порядок перечисления для свойств объекта?)). Сейчас chrome а также firefox последняя версия, кажется, перечисляет свойства в порядке определения для буквенно-цифровых свойств, но числовом порядке для числовых свойств ( следуя спецификации ES2015).

Так вот, driverA сначала подключается к источникам, запускает соответствующий поток данных. когда driverB связано с источниками, то же самое. Этот поток данных является синхронным, потому что вы написали B$, Итак, когда subscribe т.е. проводка сделана, все данные a b c d течет синхронно из B$ и когда driverC подключен, B$ уже завершено. Учитывая, что проводка выполнена с replaySubject(1)эта проводка дает вам последнее испущенное значение перед завершением, которое d,

Так что здесь из-за синхронности порядок имеет значение: если бы B и C были подключены первыми, это было бы хорошо. И из-за удачи у вас неадекватный порядок исполнения.

Чтобы убедить вас в этом, ваш код, в котором я упорядочил ваши потоки в топологическом порядке, работает так, как ожидалось:

const Cycle = require('@cycle/core');
const {Observable} = require('rx');

function main(sources) {
    const B$ = sources.driverA
        .concat(Observable.just('b'))
        .concat(Observable.just('c'))
        .concat(Observable.just('d'));

    const C$ = sources.driverB.map(x => x.toUpperCase());

    return {
        driverC: C$,
        driverB: B$,
        driverA: Observable.just('a'),
    }
}

Cycle.run(main, {
    driverA: (A$) => A$.tap(msg => console.log(msg)),
    driverB: (B$) => B$.tap(msg => console.log(msg)),
    driverC: msg$ => { msg$.subscribe(msg => console.log(msg)) }})

Как вы распространяете все три сообщения

Ну или заказывайте свои раковины в топологическом порядке, или уберите синхронность. Я добавил delay(1) чтобы поток данных продолжался на следующем тике, когда driverC уже подключен для получения следующих значений. Вероятно, это наиболее надежный вариант, поскольку топологический порядок не всегда очевиден для вычисления, как здесь, может меняться при чередовании ваших источников и зависит от перечисления свойств объекта, зависящего от браузера (!).

С другой стороны, когда нельзя избежать синхронности потока данных, вы обычно решаете проблемы с подключением, используя publish сначала подключить все источники, а затем connect так что когда данные перетекут, все источники уже готовы их получить.

Просто добавьте delay после driverA:

const B$ = sources.driverA.delay(1)

Пример WebpackBin.

Кроме того, вы можете вызвать concat один раз со всеми перечисленными наблюдаемыми и отсрочить это.

const B$ = Observable.concat(
    sources.driverA,
    Observable.just('b'),
    Observable.just('c'),
    Observable.just('d')
).delay(1);

Пример WebpackBin # 2.

Что-то, что нужно помнить, main Функция просто подключает трубы. run включает воду

Другие вопросы по тегам