部分取消可观察的

时间:2017-11-07 18:05:40

标签: rxjs observable

这是我的约束情景:

我使用react-observable在React中创建前端,并且有一些按钮可以调用API并获取结果。 按下按钮的顺序对最终结果很重要,因此不得中断通话。

每次调用都会返回一个巨大的JSON响应,它会覆盖之前的每个结果。 只应在API的最后结果中加载JSON。

举个例子:

有3个按钮:A,B和C.

  • 如果我点击A,然后点击B,然后点击C:

    程序必须调用A动作的API,等待响应在没有解析JSON的情况下可用,然后调用B动作并执行相同的操作(等待但不解析)然后在程序必须调用C动作等待并处理其结果。

  • 如果我点击B,然后点击C,然后点击A:

    结果与前一个用例不同,但过程类似:call,wait,call,wait,call,wait,read result

  • 我可以随时点击我想要的按钮。

我做了什么:

对我来说,似乎我需要使用concat顺序运行每个调用,但也可以随时取消取消任何操作。

const epic1 = action$
    .typeOf(TYPE_X)
    .mapTo({ type: START_ACTION })

const epic2 = action$
    .typeOf(TYPE_X)
    .concatMap(action =>
        postToAPI(action)
            .map((response) => Observable.from(response.json()))
            .map((data) => generate_action_to_process_data(data))
            .takeUntil(
                action$.ofType(START_ACTION)))

不幸的是,这并没有按预期工作。整个观察者被取消,并且它根本不会等待回复。甚至最糟糕的是:调用以某种方式并行执行,因为承诺本身会继续运行。

2 个答案:

答案 0 :(得分:1)

我认为这样的事情可以做到。

基本上,当第一个操作发生时,我们启动API调用并保留它的Promise(我使用toPromise来确保查询开始,因为我不知道你的postToAPI到底是什么一样)。

如果在该承诺解决之前发生了另一个动作,那么我使用then将新的API调用链接到前一个结束,并且我们停止侦听第一个承诺,而是听取链式承诺。 / p>

只要用户点击按钮,就会重复此操作。如果链式承诺完成,那么它的响应将被释放。因此,每当我们看到响应发出时,我们“重置”链,然后处理最终响应。

const actions = action$.typeOf(TYPE_X);
const epic = Observable.defer(() => {
  let current;

  return actions
    .switchMap(action => {
      if (current) {
        // run the new query after the current one finishes
        // ignore the actual response of the current query
        current = current.then(() => postToAPI(action).toPromise());
      }
      else {
        // no query running.  start this query now
        current = postToAPI(action).toPromise();
      }
    })
    // if the query chain finishes, then clear the chain
    .do(() => current = null)
    .map(response => response.json())
    .map(data => generate_action_to_process_data(data));
});

epic.subscribe(processDataActions => ...);

我们将整个构造包装在defer中,这样我们就可以为这个史诗观察者的每个订阅构造一个新的闭包变量current,这样不同的观察者就不会相互干扰。在这种情况下可能没必要,但我是为了完整起见而做的。

答案 1 :(得分:0)

根据@brandon的答案,我提出了这个解决方案:

const epicX = (action$, { getState }) => {
    let promise: Promise<any> = Promise.resolve()
    let loadResult: (response: Response) => Observable<Action>

    return action$
        .ofType(TYPE_X)
        .switchMap(action => {
            const { query, load } = process_call_to_api_x(action, getState)
            if (query === undefined) return Observable.empty()
            loadResult = load
            promise = promise.then(() => query.toPromise())
            return Observable.from(promise)
        })
        .flatMap(response => loadResult(response))
}

我认为可以简化为......

const epicX = (action$, { getState }) => {
    let promise: Promise<any> = Promise.resolve()

    return action$
        .ofType(TYPE_X)
        .switchMap(action => {
            const query: Observable<Response> = postToAPI(action)
            promise = promise.then(() => query.toPromise())
            return Observable.from(promise)
        })
        .flatMap(response => Observable.from(response.json()))
        .flatMap(data => generate_action_to_process_data(data))
}