[英]Angular: how to return the first successful response from the list of http requests
I have a list of servers urls and making sequential http requests to them in a loop.我有一个服务器 url 列表,并在循环中向它们发出顺序 http 请求。 When the success response arrives from the current request I want to break the loop and not to call all other servers.当成功响应从当前请求到达时,我想打破循环而不是调用所有其他服务器。 Could someone advice me how this could be handled in Angular/RxJS?有人可以建议我如何在 Angular/RxJS 中处理这个问题吗? Something like:就像是:
getClientData() {
for(let server of this.httpsServersList) {
var myObservable = this.queryData(server)
.pipe(
map((response: any) => {
const data = (response || '').trim();
if(data && this.dataIsCorrect(data)) {
return data; // **here I want to break from the loop!**
}
})
);
return myObservable;
}
}
private queryData(url: string) {
return this.http.get(url, { responseType: 'text' });
}
IMO it's better to avoid using a for
loop for subscribing to multiple observables. IMO 最好避免使用for
循环来订阅多个 observables。 It might lead to multiple open subscriptions.它可能会导致多个开放订阅。 Common function used for this case is RxJS forkJoin
.本案例常用的 function 是 RxJS forkJoin
。 But given your specific condition, I'd suggest using RxJS from
function with concatMap
operator to iterator each element in order and takeWhile
operator to stop based on a condition.但鉴于您的具体情况,我建议使用 function from
RxJS 和concatMap
运算符来按顺序迭代每个元素,并根据条件使用takeWhile
运算符来停止。
import { from } from 'rxjs';
import { concatMap, filter, map, takeWhile } from 'rxjs/operators';
getClientData(): Observable<any> {
return from(this.httpsServersList).pipe(
concatMap((server: string) => this.queryData(server)),
map((response: any) => (response || '').trim()),
filter((data: string) => !!data) // <-- ignore empty or undefined data (remove this statement if you need them)
takeWhile((data: string) => // <-- close stream when data is valid and condition is true
!data || !this.dataIsCorrect(data)
)
);
}
Note: Try to tweak the condition inside the takeWhile
predicate to match your requirement.注意:尝试调整takeWhile
谓词中的条件以匹配您的要求。
In angular we rely on RxJS operators for such complex calls If you want to to call all of them in parallel then once one of them is fulfilled or rejected to cancel the other calls you should use RxJS race learnrxjs.io/learn-rxjs/operators/combination/race Or without RxJS you could use Promise.race In angular we rely on RxJS operators for such complex calls If you want to to call all of them in parallel then once one of them is fulfilled or rejected to cancel the other calls you should use RxJS race learnrxjs.io/learn-rxjs/operators /combination/race 或者没有 RxJS 你可以使用 Promise.race
However if you want to call them in parallel and wait until first fulfilled "not rejected" or all of them rejected this is the case for Promise.any Unfortunately no RxJS operator for it but on the follwoing article you could see how to implement this custom operator for Promise.any and an example for that operator https://tmair.dev/blog/2020/08/promise-any-for-observables/但是,如果您想并行调用它们并等到第一次完成“未拒绝”或全部拒绝,这是 Promise.any 的情况。不幸的是,没有 RxJS 运算符,但在以下文章中,您可以看到如何实现此自定义Promise.any 的运算符和该运算符的示例https://tmair.dev/blog/2020/08/promise-any-for-observables/
You can't use race because it will call all URLs in parallel, but you can use switchMap with recursive implementation您不能使用race ,因为它会并行调用所有 URL,但您可以使用带有递归实现的switchMap
import { of, Observable, throwError } from 'rxjs';
import { catchError, switchMap } from 'rxjs/operators'
function getClientData(urls: string[]) {
// check if remaining urls
if (!urls.length) throw throwError(new Error('all urls have a error')); ;
return queryData(urls[0]).pipe(
switchMap((response) => {
const data = (response || '').trim();
if(data && this.dataIsCorrect(data))
// if response is correct, return an observable with the data
// for that we use of() observable
return of(data)
// if response is not correct, we call one more time the function with the next url
return getClientData(urls.slice(1))
}),
catchError(() => getClientData(urls.slice(1)))
);
}
function queryData(url: string): Observable<unknown> {
return this.http.get(url, { responseType: 'text' });
}
create a subject like this创建这样的主题
responseArrived=new Subject();
and after pipe add takeuntil like this并在 pipe 之后像这样添加 takeuntil
var myObservable = this.queryData(server).pipe(takeUntil(responseArrived),map...
and in the line of code return data just call并在代码行中返回数据只需调用
responseArrived.next()
If your only condition is that you cancel requests once at least one response is received, can't just simply unsubscribe from the observable returned from the HttpClient
call?如果您的唯一条件是在收到至少一个响应后取消请求,那么不能简单地取消订阅从HttpClient
调用返回的 observable 吗?
getData() {
const subscriptions = [];
[
'https://reqres.in/api/products/1',
'https://reqres.in/api/products/2',
'https://reqres.in/api/products/3',
].forEach((url, i) => {
subscriptions[i] = this.getClientData(url).subscribe(() => {
// Unsubscribe
subscriptions.forEach((v, j) => {
if (j !== i) {
console.log('Unsubscribe from ', j);
v.unsubscribe();
}
});
});
});
}
private getClientData(url: string) {
return this.httpClient.get(url, { responseType: 'text' }).pipe(
map((response: any) => {
const data = (response || '').trim();
if (data && true) return data;
return null;
})
);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.