[英]How to debounce async validator in Angular 4 with RxJS observable?
[英]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.