簡體   English   中英

Angular:如何消除可觀察對象的反跳?

[英]Angular : how to debounce an Observable?

在我的應用程序中,我有一個返回如下所示可觀察值的服務:

public genericService(params) {
    //Do some stuff
    //...

    return this.http.post('http://foo.com', params)
        .map((response) => {
            //Do some generic stuff
            //...

            return someData;
        })
        .catch((error: any) => {
            //Manage error in a generic way + do some generic stuff
            //...

            return Observable.throw(error);
        });
}

let debouncePointer = debounceObservable(genericService, 200);

public genericServiceDebounce(params) {
    return debouncePointer(params);
}

現在在另一個地方,我想這樣調用我的函數

genericServiceDebounce(params)
    .subscribe((response) => {
        //Do some non-generic stuff
    }, (error) => {
        //Manage error in a non-generic way + do some non-generic stuff
    });

但是我沒有成功實現debounceObservable()函數。

我基於Promise等效項( https://github.com/moszeed/es6-promise-debounce/blob/master/src/es6-promise-debounce.js )嘗試了此實現:

debounceObservable(callback, delay, immediate?) {
    let timeout;
    return function () {
        let context = this, args = arguments;

        return Observable.create((observer) => {
            let later = function () {
                timeout = null;

                if(!immediate) {
                    observer.next(callback.apply(context, args));
                    //observer.onCompleted(); // don't know if this is needed
                }
            };
            let callNow = immediate && !timeout;
            clearTimeout(timeout);
            timeout = setTimeout(later, delay);

            if(callNow) {
                observer.next(callback.apply(context, args));
                //observer.onCompleted(); // don't know if this is needed
            }
        });
    }
}

但這不能按預期工作。 使用Promises時,返回resolve(anotherPromise)可以調用:

genericServiceDebounce().then(response => {

})

使用Observables時,返回observer.next(anotherObservable)返回一個嵌入式的observable,這意味着您應該調用:

genericServiceDebounce().subscribe(obs => {
    obs.subscribe(response => {

    })
})

您將如何實現debounceObservable()函數? (以類似Promise的方式)

澄清1:我發現了Observable.debounce()函數,但是這會反跳觀察者而不是可觀察者本身。 我想對可觀察到的信號進行反跳

澄清2:我將防抖放在服務端,因為它是一個單例,並且它們是多個調用方。 如果我將其放在呼叫方,則每個呼叫方都會有一個不同的防抖動計時器。

編輯:這是一個片段,我試圖解釋我的問題。 只需單擊不同的按鈕即可查看不同的行為(有關js代碼注釋的更多說明)。

Observable.debounce顯示RxJs中的.debounce()如何工作。 它僅輸出“ 3”,但我想要“ 1”,“ 2”,“ 3”。

Observable.debounce x3顯示了如果我在沒有將整個函數包裝在反跳中的情況下調用了3次代碼會發生什么。

可觀察的包裝x3顯示了我想要獲得的內容。 我的整個功能都已包裝好,但是如果您看一下代碼,則預訂部分是精挑細選的。

Promise x3顯示了使用Promises時的簡單程度。

 let log = (logValue) => { const list = document.querySelector('#logs'); const li = document.createElement('li'); li.innerHTML = logValue; list.appendChild(li); } /* ************************ */ /* WITH OBSERVABLE.DEBOUNCE */ /* ************************ */ let doStuffObservable = () => { Rx.Observable.create((observer) => { log('this should be called only one time (observable.debounce)'); setTimeout(() => { observer.next('observable.debounce 1'); observer.next('observable.debounce 2'); observer.next('observable.debounce 3'); }, 1000); }) .debounce(500) .subscribe((response) => { log(response); }, (error) => { log(error); }); } /* *********************************** */ /* WITH OBSERVABLE WRAPPED IN DEBOUNCE */ /* *********************************** */ let doStuffObservable2 = (param) => { return Rx.Observable.create((observer) => { log('this should be called only one time (observable wrapped)'); setTimeout(() => { observer.next('observable wrapped ' + param); }, 1000); }) } let debounceObservable = (callback, delay, immediate) => { let timeout; return function () { let context = this, args = arguments; return Rx.Observable.create((observer) => { let later = function () { timeout = null; if(!immediate) { observer.next(callback.apply(context, args)); } }; let callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, delay); if(callNow) { observer.next(callback.apply(context, args)); } }); } } let doStuffObservable2Debounced = debounceObservable(doStuffObservable2); /* ************* */ /* WITH PROMISES */ /* ************* */ let doStuffPromise = (param) => { return new Promise((resolve, reject) => { log('this should be called only one time (promise)'); setTimeout(() => { resolve('promise ' + param); }, 1000); }); } let debouncePromise = (callback, delay, immediate) => { let timeout; return function () { let context = this, args = arguments; return new Promise(function (resolve) { let later = function () { timeout = null; if (!immediate) { resolve(callback.apply(context, args)); } }; let callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, delay); if (callNow) { resolve(callback.apply(context, args)); } }); } } /* ******* */ /* SAMPLES */ /* ******* */ function doObservableDebounce() { doStuffObservable(); // result : // this should be called only one time (observable.debounce) // observable.debounce 3 // this is not what i want, i want all three values in output } function doObservableDebounce3Times() { doStuffObservable(); doStuffObservable(); doStuffObservable(); // result : // this should be called only one time (observable.debounce) // this should be called only one time (observable.debounce) // this should be called only one time (observable.debounce) // observable.debounce 3 // observable.debounce 3 // observable.debounce 3 // this is bad } function doObservableWrappedDebounce3Times() { doStuffObservable2Debounced(1) .subscribe((response) => { log(response); response.subscribe((response2) => { log(response2); }, (error) => { log(error); }) }, (error) => { log(error); }); doStuffObservable2Debounced(2) .subscribe((response) => { log(response); response.subscribe((response2) => { log(response2); }, (error) => { log(error); }) }, (error) => { log(error); }); doStuffObservable2Debounced(3) .subscribe((response) => { log(response); response.subscribe((response2) => { log(response2); }, (error) => { log(error); }) }, (error) => { log(error); }); // result : // AnonymousObservable { source: undefined, __subscribe: [Function] } // this should be called only one time (observable wrapped) // observable wrapped 3 // this is good but there are 2 embedded subscribe } function doPromiseDebounce3Times() { let doStuffPromiseDebounced = debouncePromise(doStuffPromise); doStuffPromiseDebounced(1).then(response => { log(response); }) doStuffPromiseDebounced(2).then(response => { log(response); }) doStuffPromiseDebounced(3).then(response => { log(response); }) // result : // this should be called only one time (promise) // promise 3 // this is perfect } 
 <!DOCTYPE html> <html> <head> <script data-require="rxjs@4.0.6" data-semver="4.0.6" src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.6/rx.all.js"></script> </head> <body> <button onclick='doObservableDebounce()'>Observable.debounce</button> <button onclick='doObservableDebounce3Times()'>Observable.debounce x3</button> <button onclick='doObservableWrappedDebounce3Times()'>Observable wrapped x3</button> <button onclick='doPromiseDebounce3Times()'>Promise x3</button> <ul id="logs"></ul> </body> </html> 

抱歉,您的回復我沒有收到任何通知。

對於此問題,更純凈的純Rx解決方案是將服務調用視為事件流,如下所示:

constructor() {
    this._genericServiceCall$ = new ReplaySubject(1);
    this._genericServiceResult$ = this._genericServiceCall$
        .asObservable()
        .debounceTime(1000)
        .switchMap(params => this._genericService(params));
}

private _genericService(params) {
    //Do some stuff
    //...

    return this.http.post('http://foo.com', params)
        .map((response) => {
            //Do some generic stuff
            //...

            return someData;
        })
        .catch((error: any) => {
            //Manage error in a generic way + do some generic stuff
            //...

            return Observable.throw(error);
        });
}

public genericService(params) {
    this._genericServiceCall$.next(params);
    return this._genericServiceResult$; // Optionally add `.take(1)` so the observer has the expected behaviour of only getting 1 answer back
}

我在其中看到了一些東西……您將接受哪些params作為必須通過私有_genericService獲得的_genericService

無論如何,您是否遵循這里發生的事情? 因此,每次有人調用genericService() ,都不會立即調用該服務,而是會發出一個新的_genericServiceCall$並返回_genericServiceResult$流。 如果我們看一下如何定義此流,我們會看到它需要去抖動的_genericServiceCall$ ,然后將其映射到服務調用。 從理論上講,它應該起作用-尚未嘗試。

編輯:現在,我看到-您可能需要發布genericServiceResult使其成為可觀察的熱點,否則任何觀察者訂閱后,它將立即返回:

constructor() {
    this._genericServiceCall$ = new ReplaySubject(1);
    this._genericServiceResult$ = this._genericServiceCall$
        .asObservable()
        .debounceTime(1000)
        .switchMap(params => this._genericService(params))
        .publish();
    const subscription = this._genericServiceResult$.connect();
    // You must store subscription somewhere and dispose it when this object is destroyed - If it's a singleton service this might not be needed.
}

好吧,我想我找到了辦法。 我應該做的是替換:

observer.next(callback.apply(context, args));

通過

callback.apply(context, args).subscribe((response) => {
        observer.next(response)
    }, (error) => {
        observer.error(error);
    });

最后,它可以像經典的observable一樣使用:

debouncedObservable(1)
    .subscribe((response) => {
        log(response);
    }, (error) => {
        log(error);
    });

這是實現的摘要:

 let log = (logValue) => { const list = document.querySelector('#logs'); const li = document.createElement('li'); li.innerHTML = logValue; list.appendChild(li); } /* *********************************** */ /* WITH OBSERVABLE WRAPPED IN DEBOUNCE */ /* *********************************** */ let doStuffObservable = (param) => { return Rx.Observable.create((observer) => { log('this should be called only one time (observable wrapped)'); setTimeout(() => { observer.next('observable wrapped ' + param); }, 1000); }) } let debounceObservable = (callback, delay, immediate) => { let timeout; return function () { let context = this, args = arguments; return Rx.Observable.create((observer) => { let later = function () { timeout = null; if(!immediate) { callback.apply(context, args).subscribe((response) => { observer.next(response) }, (error) => { observer.error(error); }); } }; let callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, delay); if(callNow) { callback.apply(context, args).subscribe((response) => { observer.next(response) }, (error) => { observer.error(error); }); } }); } } let doStuffObservable2Debounced = debounceObservable(doStuffObservable); /* ******* */ /* SAMPLES */ /* ******* */ function doObservableWrappedDebounce3Times() { doStuffObservable2Debounced(1) .subscribe((response) => { log(response); }, (error) => { log(error); }); doStuffObservable2Debounced(2) .subscribe((response) => { log(response); }, (error) => { log(error); }); doStuffObservable2Debounced(3) .subscribe((response) => { log(response); }, (error) => { log(error); }); } 
 <!DOCTYPE html> <html> <head> <script data-require="rxjs@4.0.6" data-semver="4.0.6" src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.6/rx.all.js"></script> </head> <body> <button onclick='doObservableWrappedDebounce3Times()'>Observable wrapped x3</button> <ul id="logs"></ul> </body> </html> 

如果您認為我錯過了一些東西,請發表評論。

暫無
暫無

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

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