簡體   English   中英

RxJS自定義操作員內部變量

[英]RxJS Custom Operator Internal Variables

在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()

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通知中接收不同的值,因為操作符內的狀態是共享的。

例如,如果source observable是同步的,則連續訂閱將看到不同的值:

 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運算符非常相似的運算符,並使其在所有情況下都能正常運行。 為此,必須確保運營商內的任何州都是按訂閱

可管理運算符只是一個帶有observable並返回observable的函數,因此您可以使用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> 

正如您已經說過的,您將失去純函數的一些優點。 在這種特殊情況下,您可能會遇到后期訂閱者獲得的數據流不同於您預期的風險(取決於您在實際案例中所做的與此構建的數據相比)。

例如,通過添加延遲訂戶,流'A'將看到0和1.流'B'將僅看到'1'(它跳過0,因為來自'A'訂戶的obs仍然是活動的。流'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)

這是否可接受或預期的行為取決於您的使用案例。 雖然嘗試使用純函數來獲得所有優點是很好的,但有時它對您的用例來說是不實際或不合適的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM