RxJS Пользовательский Оператор Внутренние Переменные - PullRequest
0 голосов
/ 25 августа 2018

Есть ли недостатки в использовании / изменении переменной из пользовательского замыкания оператора в RxJS? Я понимаю, что это нарушает принцип «чистой» функции и что вы можете использовать scan для этого простого примера, но я специально задаю конкретные материальные проблемы с нижележащей схемой ниже:

const custom = () => {

  let state = 0; 

  return pipe(
    map(next => state * next),
    tap(_ => state += 1),
    share()
  )
}

// Usage
const obs = interval(1000).pipe(custom())

obs.subscribe()

Ответы [ 2 ]

0 голосов
/ 26 августа 2018

Существуют по крайней мере две проблемы с тем, как вы сохранили состояние в вашем операторе custom.

Первая проблема заключается в том, что ваши действия означают, что оператор больше не является ссылочно-прозрачным.То есть, если вызов оператора заменяется возвращаемым значением оператора, поведение будет другим:

const { pipe, range } = rxjs;
const { map, share, tap } = rxjs.operators;

const custom = () => {
  let state = 0; 
  return pipe(
    map(next => state * next),
    tap(_ => state += 1),
    share()
  );
};

const op = custom();
console.log("first use:");
range(1, 2).pipe(op).subscribe(n => console.log(n));
console.log("second use:");
range(1, 2).pipe(op).subscribe(n => console.log(n));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://unpkg.com/rxjs@6/bundles/rxjs.umd.min.js"></script>

Вторая проблема, как упоминалось в другом ответе, заключается в том, что разные подписки будут получать разные значения в своих next уведомлениях в качестве состояниявнутри оператора является общим.

Например, если наблюдаемый источник является синхронным, последовательные подписки будут видеть различные значения:

const { pipe, range } = rxjs;
const { map, share, tap } = rxjs.operators;

const custom = () => {
  let state = 0; 
  return pipe(
    map(next => state * next),
    tap(_ => state += 1),
    share()
  );
};

const source = range(1, 2).pipe(custom());
console.log("first subscription:");
source.subscribe(n  => console.log(n));
console.log("second subscription:");
source.subscribe(n => console.log(n));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://unpkg.com/rxjs@6/bundles/rxjs.umd.min.js"></script>

Однако можно написать оператор, очень похожий на ваш оператор custom, и он будет работать правильно при любых обстоятельствах.Для этого необходимо убедиться, что любое состояние внутри оператора равно для каждой подписки .

Трубный оператор - это просто функция, которая принимает наблюдаемое и возвращает наблюдаемое, так что вы можетеиспользуйте defer, чтобы убедиться, что ваш штат соответствует подписке, например:

const { defer, pipe, range } = rxjs;
const { map, share, tap } = rxjs.operators;

const custom = () => {
  return source => defer(() => {
    let state = 0; 
    return source.pipe(
      map(next => state * next),
      tap(_ => state += 1)
    );
  }).pipe(share());
};

const op = custom();
console.log("first use:");
range(1, 2).pipe(op).subscribe(n => console.log(n));
console.log("second use:");
range(1, 2).pipe(op).subscribe(n => console.log(n));

const source = range(1, 2).pipe(op);
console.log("first subscription:");
source.subscribe(n => console.log(n));
console.log("second subscription:");
source.subscribe(n => console.log(n));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://unpkg.com/rxjs@6/bundles/rxjs.umd.min.js"></script>
0 голосов
/ 25 августа 2018

Как вы уже заявили, вы теряете некоторые преимущества чистых функций.В этом конкретном случае вы рискуете получить покойных подписчиков с потоками данных, отличными от ожидаемых (зависит от того, что вы делаете в реальном деле, а не в этом построенном).

Например,При добавлении поздних подписчиков поток «A» будет видеть 0 и 1. Поток «B» будет видеть только «1» (он пропускает 0, потому что obs все еще активен от абонента «A». Поток «C» будет вести себя как поток «A»).'.

const { interval, pipe, subscribe } = Rx;
const { take, map, tap, share  } = RxOperators;

const custom = () => {
  let state = 0; 
  return pipe(
    map(next => state * next),
    tap(_ => state += 1),
    share()
  )
}

// Late subscribers can get different streams
const obs = interval(500).pipe(custom())
const sub1 = obs.pipe(take(2)).subscribe((x) => console.log('A', x))
setTimeout(() => obs.pipe(take(1)).subscribe((x) => console.log('B', x)), 500)
setTimeout(() => obs.pipe(take(3)).subscribe((x) => console.log('C', x)), 3000)

Является ли это приемлемым или ожидаемым поведением, будет зависеть от вашего варианта использования. Хотя полезно использовать чистые функции для всех их преимуществ, иногда это не практично или не подходит дляваш вариант использования.

...