在rxjs中巧妙的蒸汽可观察合并

Tricky steam observable merge in rxjs

本文关键字:观察 合并 rxjs      更新时间:2023-09-26

我需要一些帮助。我正在尝试一个实验,在按钮的mouseenters上启动ajax请求,但只有在用户单击时才会使用该请求的结果(或者,如果用户单击时请求尚未完成,则在完成后立即使用结果)。如果用户未单击就离开了按钮,则应取消请求。

我遇到的问题是如何将点击流与请求流合并。我不能与LatestFrom一起使用,因为如果请求在点击后完成,它将不会被消耗。我也不能使用combineLatest,因为如果过去发生过任何点击,请求都会被消耗掉,即使我现在只是鼠标悬停。希望得到一些指导。这是一个有趣的问题,但我被卡住了

const fetchContent = (url) => {
  const timeDelay$ = Rx.Observable.timer(1000); // simulating a slow request
  const request$ =  Rx.Observable.create(observer =>
    fetch(url, { mode: 'no-cors' })
      .then(json => {
         observer.onNext('res')
         observer.onCompleted()
      })
      .catch(e => observer.onError())
  )
  return timeDelay$.concat(request$)
}
const hover$ = Rx.Observable.fromEvent(myButton, 'mouseenter')
const leave$ = Rx.Observable.fromEvent(myButton, 'mouseleave')
const click$ = Rx.Observable.fromEvent(myButton, 'click')
const hoverRequest$ = hover$
  .flatMap(e =>
    fetchContent(e.target.getAttribute('href'))
      .takeUntil(leave$.takeUntil(click$))
  )
const displayData$ = click$
  .combineLatest(hoverRequest$)
displayData$.subscribe(x => console.log(x))

实际上,您离这里并不遥远。你只是真的错过了zip的加入。由于传播真正需要的是鼠标点击来完成请求。通过压缩请求和鼠标点击事件,您可以确保两者都不会在没有另一个的情况下发出。

const hover$ = Rx.Observable.fromEvent(myButton, 'mouseenter');
const leave$ = Rx.Observable.fromEvent(myButton, 'mouseleave');
const click$ = Rx.Observable.fromEvent(myButton, 'click');

//Make sure only the latest hover is emitting requests
hover$.flatMapLatest(() => {
  //The content request
  const pending$ = fetchContent();
  //Only cancel on leave if no click has been made
  const canceler$ = leave$.takeUntil(click$);
  //Combine the request result and click event so they wait for each other
  return Rx.Observable.zip(pending$, click$, (res, _) => res)
               //Only need the first emission
               .take(1)
               //Cancel early if the user leaves the button
               .takeUntil(canceler$);
});

也许您可以将其概念化为三个事件(悬停、离开、点击),触发三个操作(发出请求、取消请求、传递请求结果)修改状态(请求?传递请求?)。

现在,这是在周日晚上匆忙完成的,但如果运气好的话,这样的事情可能会奏效:

function label(str) {return function(x){var obj = {}; obj[str] = x; return obj;}}
function getLabel(obj) {return Object.keys(obj)[0];}
const hover$ = Rx.Observable.fromEvent(myButton, 'mouseenter').map(label('hover'));
const leave$ = Rx.Observable.fromEvent(myButton, 'mouseleave').map(label('leave'));
const click$ = Rx.Observable.fromEvent(myButton, 'click').map(label('click'));
var initialState = {request : undefined, response : undefined, passResponse : false};
var displayData$ = Rx.Observable.merge(hover$, leave$, click$)
  .scan(function (state, intent){
          switch (getLabel(intent)) {
            case 'hover' : 
              if (!state.request) {
                state.request = someRequest;
                state.response$ = Rx.Observable.fromPromise(executeRequest(someRequest));
              }
            break;
            case 'leave' : 
              if (state.request && !state.passResponse) cancelRequest(someRequest);
              state.passResponse = false;
            break;
            case 'click' :
              if (!state.request) {
                state.response$ = Rx.Observable.fromPromise(executeRequest(someRequest));
              }
              state.passResponse = true;
          }
        }, initial_state)
  .filter(function (state){return state.passResponse;})
  .pluck('response$')
  .concatAll()