Angular / Rxjs - 排队/阻止HTTP请求,直到前一个请求完成

时间:2018-03-29 15:02:27

标签: angular rxjs

我正在与使用一次性身份验证令牌的API服务器交谈,每次请求都会重新发布(即生成新令牌)。如果使用相同的令牌同时发出两个API请求,则由于令牌过期而导致失败,并且可能会不必要地触发错误处理程序。

我想要做的是构建一个等待机制,因此一次只发出一个HTTP请求,后续请求排队,直到完成前一个请求。

我想以通用的方式构建它,所以我可以有一个服务层来发出请求,所以它对上层是透明的,但我很难找到一个巧妙的方法来做到这一点。有什么建议吗?

-- edit --

我应该提到所有后续的API调用URL /请求主体都直接依赖于先前API调用的响应,例如: GET /books?apiKey=ABC返回:

{ 'apiKey': 'XYZ', 'names': [...] }

排队的下一个请求必须等待此响应并附加新的apiKey:GET /authors?apiKey=XYZ

在上面的示例中,执行GET /authors?apiKey=ABC会 导致错误。

如果两个HTTP请求之间存在强烈的依赖关系(例如请求书,然后根据其id请求该特定书籍的作者),则可以使用flatMap,嵌套订阅等来序列化它们。我需要类似的功能但是以通用的方式,我可以将请求添加到在运行时序列化的队列。

-- edit two --

我有两个组件,如下所示,假设两者都在启动时调用API调用。

class ComponentA { 
    ngOnInit(api:<BookApi extends CommonApi>) {
        this.api.list().subscribe(x => {...});
    }
}

class ComponentB { 
    ngOnInit(api:<CityLookupApi extends CommonApi>) {
        this.api.list().subscribe(x => {...});
    }
}

所有API服务都扩展了一个通用API来隐藏其背后的apiKey处理,因此组件可以使用更高级别的调用来帮助理解。问题是,如果两个组件几乎同时初始化,则两个调用都可以使用相同的api密钥,一个将失败。但是,我无法使用例如前面的方法创建这些Observable批次。 forkJoin创建并在运行时异步订阅,因为它们是在单独的组件中创建的。

3 个答案:

答案 0 :(得分:2)

Yanis-git解决了排队问题。如果要在发生其他请求时阻止所有请求,请使用exhaustMap

exhaustmap docs

答案 1 :(得分:1)

您可以使用Observable.concat(...myArrayOfAjaxRequestObservable)逐个使用每个observable。

只有Observable.contact必须自己订阅你的ajax请求。是100%确保请求不会被代码的另一部分触发的唯一方法。

此处提供更多信息:https://www.learnrxjs.io/operators/combination/concat.html

--- --- UPDATE

示例:https://stackblitz.com/edit/angular-66ic6c?file=app%2Fapp.component.ts

答案 2 :(得分:1)

我会考虑将mergeMap与其concurrency参数一起使用。

这是一段模拟我想要的代码片段

import {Observable} from 'rxjs';
import {Subject} from 'rxjs';

const requestsRemoteService = new Subject<string>();
const requestStream = requestsRemoteService.asObservable();

// this function simulates the execution of the request
const requestExecution = (input: string) => {
    return Observable.of(input + ' executed').delay(2000);
}

// this is what the service should to queue the requests
requestStream
// the last parameter, set to 1, is the level of concurrency
.mergeMap(requestInput => requestExecution(requestInput), 1)
.subscribe(
    data => console.log(data),
    err => console.error(err),
    () => console.log('DONE')
)

// these are the various requests coming to the service
setTimeout(() => requestsRemoteService.next('First request'), 1);
setTimeout(() => requestsRemoteService.next('Second request'), 2);
setTimeout(() => requestsRemoteService.next('Third request'), 3);