取消订阅后取消并发HTTP请求

时间:2018-11-08 23:25:07

标签: angular typescript rxjs google-api-client rxjs6

我必须解决以下问题:

由于Google API的限制,许多API调用都是通过API接口(Google API)进行的,因此每秒/并发请求的数量受到限制。

我使用一个主题(接收器 /调用池),该主题使用mergeMap管理所有API请求,并将结果返回给另一个管道主题。

由于API请求可以在完成之前退订,因此不应阻塞我的接收器。因此,我必须在取消订阅后停止API请求(任务)。

问题: 我不知道如何正确捕获此未订阅状态。我目前要做的是覆盖 subscribe unsubscribe 以捕获此状态。它可以工作,但对我来说看起来并不“ rxjs”。

我可以改善什么?

import {Observable, Subject, Subscription, Subscribable, EMPTY} from 'rxjs';
import {mergeMap, tap} from 'rxjs/operators';

function doHeavyRequest() {
    return new Observable(subscribe => {
        // Simulate delay.
        setTimeout(() => {
            subscribe.next(1);
            subscribe.complete();
        }, 1000);
    });
}

const sink = new Subject<[Subject<any>, number]>();

sink.pipe(
    mergeMap(([subject, id]) => {
        // Stop request here if already unsubscribed.
        if (subject.closed) {
            console.log('Request cancelled:', id);
            return EMPTY;
        }
        return doHeavyRequest()
            .pipe(
                tap(res => {
                    if (!subject.closed) {
                        subject.next(res);
                        subject.complete();
                    } else {
                        console.log('Request aborted:', id);
                    }
                })
            );
    }, 2)
).subscribe();

// Insert request into sink.
// Overwrite subscribe and unsubscribe.
// Track unsubscribe over the flag alive.
function getSomething(id: number) {
    const task = new Subject();

    const ob = task.asObservable();

    ob.subscribe = (...args: any[]) => {
        const sub = Observable.prototype.subscribe.call(ob, ...args);
        sub.unsubscribe = () => {
            if (!task.isStopped)
                task.unsubscribe();
            Subscription.prototype.unsubscribe.call(sub);
    };
        return sub;
    };

    sink.next([task, id]);

    return ob;
}

// Make 3 requests and unsubscribe.
export function test() {
    const ob0 = getSomething(0);
    const ob1 = getSomething(1);
    const ob2 = getSomething(2);

    const sub0 = ob0.subscribe(e => {
        console.log('0:', e);
    });
    setTimeout(() => sub0.unsubscribe(), 1500);

    const sub1 = ob1.subscribe(e => {
        console.log('1:', e);
    });
    setTimeout(() => sub1.unsubscribe(), 900);

    const sub2 = ob2.subscribe(e => {
        console.log('2:', e);
    });
    setTimeout(() => sub2.unsubscribe(), 100);
}

请参见plunker上的 test.ts 和控制台输出:

https://next.plnkr.co/edit/KREjMprTrjHu2zMI?preview

2 个答案:

答案 0 :(得分:1)

我不确定我是否理解正确,但是您似乎想在取消订阅后进行一些清理,对吗?

您可以将拆解逻辑添加到单个订阅中,如下所示:

const subscription = obs.subscribe(() => {...})
subscription.add(() => { /* do cleanup here. This is executed upon unsubscribing. */})

也许finalize可管道运算符也可能有用。这在完成时向可观察对象添加了逻辑,大多数情况下,这是在完成或取消订阅后进行的。请注意,热观测值会有所不同。

创建可观察对象时,您还可以通过从其内部逻辑函数返回一个函数来向其中添加拆解逻辑,就像对finalize进行管道操作一样:

const obs = new Observable(subject => { /* subject.next/error/complete somewhere */
  return () => { /* cleanup resources upon unsubscribe OR complete */ }
})

答案 1 :(得分:0)

感谢@ Badashi,使用finalize可以使外观看起来更好:

import {Observable, Subject, Subscription, Subscribable, EMPTY} from 'rxjs';
import {mergeMap, tap, finalize} from 'rxjs/operators';

function doHeavyRequest() {
    return new Observable(subscribe => {
        // Simulate delay.
        setTimeout(() => {
            subscribe.next(1);
            subscribe.complete();
        }, 1000);
    });
}

const sink = new Subject<[Subject<any>, number]>();

sink.pipe(
    mergeMap(([subject, id]) => {
        // Stop request here if already unsubscribed.
        if (subject.closed) {
            console.log('Request cancelled:', id);
            return EMPTY;
        }
        return doHeavyRequest()
            .pipe(
                tap(res => {
                    if (!subject.closed) {
                        subject.next(res);
                        subject.complete();
                    } else { 
                        console.log('Request aborted:', id);
                    }
                })
            );
    }, 2)
).subscribe();

// Insert request into sink.
// Overwrite subscribe and unsubscribe.
// Track unsubscribe.
function getSomething(id: number) {
    const task = new Subject();
    const ob = task.pipe(finalize(() => {
        if (!task.isStopped) {
            task.unsubscribe();
        }
    }));

    sink.next([task, id]);

    return ob;
}

// Make 3 requests and unsubscribe.
export function test() {
    const ob0 = getSomething(0);
    const ob1 = getSomething(1);
    const ob2 = getSomething(2);

    const sub0 = ob0.subscribe(e => {
        console.log('0:', e);
    });
    setTimeout(() => sub0.unsubscribe(), 1500);

    const sub1 = ob1.subscribe(e => {
        console.log('1:', e);
    });
    setTimeout(() => sub1.unsubscribe(), 900);

    const sub2 = ob2.subscribe(e => {
        console.log('2:', e);
    });
    setTimeout(() => sub2.unsubscribe(), 100);
}

输出:

0: 1
Request cancelled: 2
Request aborted: 1