我有一个为我处理多个API请求的函数,如果失败,则将每个请求置于重试模式。现在,如果一个请求已经在重试循环中,并且同一API调用的另一个实例到来,我的函数将无法跟踪此请求,并再次在重试循环中添加冗余的API调用。
Assuming i am placing a call to
/api/info/authors
What is happening
1stREQ| [re0]------>[re1]------>[re2]------>[re3]------>[re4]------>[re5]
2ndREQ| [re0]------>[re1]------>[re2]------>[re3]------>[re4]------>[re5]
What should happen,
1stREQ| [re0]------>[re1]------>[re2]------>[re3]------>[re4]------>[re5]
2ndREQ| [re0]/ (MERGE)
以下是我的服务有我的重试功能,
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { retryWhen, mergeMap, finalize, share, shareReplay } from 'rxjs/operators';
import { Observable, throwError, of, timer } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class DataService {
constructor(private http: HttpClient) { }
private apiUrl:string = 'http://localhost/api-slim-php/public/api';
public dataServerURL:string = 'http://localhost/';
/*
This function fetches all the info from API /info/{category}/{id}
category : author & id : '' or 1,2,3... or a,b,c...
category : form & id : '' or 1,2,3...
category : location & id : '' or 1,2,3...
category : school & id : '' or 1,2,3...
category : timeframe & id : '' or 1,2,3...
category : type & id : '' or 1,2,3...
*/
public getInfoAPI(category:string, id:string = "", page:string = "1", limit:string = "10"){
var callURL : string = '';
if(!!id.trim() && !isNaN(+id)) callURL = this.apiUrl+'/info/'+category+'/'+id;
else callURL = this.apiUrl+'/info/'+category;
return this.http.get(callURL,{
params: new HttpParams()
.set('page', page)
.set('limit', limit)
}).pipe(
retryWhen(genericRetryStrategy({maxRetryAttempts: 5, scalingDuration: 1000})),
shareReplay()
);
}
}
export const genericRetryStrategy = ({
maxRetryAttempts = 3,
scalingDuration = 1000,
excludedStatusCodes = []
}: {
maxRetryAttempts?: number,
scalingDuration?: number,
excludedStatusCodes?: number[]
} = {}) => (attempts: Observable<any>) => {
return attempts.pipe(
mergeMap((error, i) => {
const retryAttempt = i + 1;
// if maximum number of retries have been met
// or response is a status code we don't wish to retry, throw error
if (
retryAttempt > maxRetryAttempts ||
excludedStatusCodes.find(e => e === error.status)
) {
console.log(error);
return throwError(error);
}
console.log(
`Attempt ${retryAttempt}: retrying in ${retryAttempt *
scalingDuration}ms`
);
// retry after 1s, 2s, etc...
return timer(retryAttempt * scalingDuration);
}),
finalize(() => console.log('We are done!'))
);
};
注意:
有些人可能建议我执行shareReplay()
,但它无法处理来自其他两个组件/源的相同请求
以下应仅为6,相反,在快速单击调用相同API的两个按钮时,它们为12。 (缩放持续时间为1000ms)
注:
请避免使用FLAGS
,在我看来这是最终的威胁。
注意,每次调用getInfoAPI()
http.get()
创建一个新的可观察对象,并且shareReplay()
共享该新的可观察对象,它不会合并两个调用。如果希望呼叫者获得合并的可观测值,则可以从两个调用中返回相同的可观测值。但这是错误的解决方案,我将在后面解释。例如:
export class DataService {
private readonly getInfoRequest = new Subject<GetInfoRequest>();
private readonly getInfoResponse = this.getInfoRequest.pipe(
exhaustMap(request => {
const callURL = createGetInfoUrl(request);
const callParams = createGetInfoParams(request);
return this.http.get(callURL, callParams).pipe(
retryWhen( ... );
);
})
);
public getInfoAPI(category:string, id:string = "", page:string = "1", limit:string = "10") {
this.getInfoRequest.next({ category: category, id: id, page: page, limit: limit });
return this.getInfoResponse;
}
...
}
上面的代码具有与您尝试通过shareReplay()
实现的功能相同的功能,但是如果调用参数不匹配怎么办?一个组件已请求第一页,但另一个组件已请求第二页,第二个组件将接收第一页而不是第二个页面。因此,我们也应该考虑调用参数,事情变得更加复杂。
一种解决方案是用存储库包装HttpService,该存储库将处理缓存,它可以将数据缓存在内存中,数据库内部或其他地方,但是我怀疑这就是您想要的。据我了解,问题是并发请求,更好的方法是阻止此类请求。例如,如果某个请求是通过单击按钮触发的,则只需在执行请求时禁用该按钮,或跳过重复的请求即可。这是解决此类问题的常用方法。