如何实现连续的 API 调用取决于调用的内聚程度。
我的意思是您是否将这两个调用视为单个'事务',其中两个请求都必须成功才能成功更改您的状态。
显然,如果第一个请求失败,则无法启动第二个请求,因为它依赖于第一个请求的数据。 但是...
第一个请求成功,第二个请求失败,应该怎么办?
您的应用能否继续使用第一个请求中的 id 而没有第二个请求,或者它最终会处于不一致的状态?
我将介绍两种情况:
-
场景 1:当任一请求失败时,您将其视为整个“事务”失败,因此不必关心哪个请求失败。
-
场景二:当请求1失败时,请求2不会被执行。当请求 2 失败时,请求 1 仍将被视为成功。
场景 1
由于两个请求都必须成功,您可以将这两个请求视为一个请求。
在这种情况下,我建议隐藏服务中的连续调用(这种方法不是特定于ngrx/redux,它只是普通的RxJs):
@Injectable()
export class PostService {
private API_URL1 = 'http://your.api.com/resource1';
private API_URL2 = 'http://your.api.com/resource2';
constructor(private http: Http) { }
postCombined(formValues: { name: string, age: number }): Observable<any> {
return this.http.post(this.API_URL1, { name: formValues.name })
.map(res => res.json())
.switchMap(post1result =>
this.http.post(this.API_URL2, {
/* access to post1result and formValues */
id: post1result.id,
age: formValues.age,
timestamp: new Date()
})
.map(res => res.json())
.mergeMap(post2result => Observable.of({
/* access to post1result and post2result */
id: post1result.id,
name: post1result.name,
age: post2result.age,
timestamp: post2result.timestamp
})
);
}
}
现在您可以使用 postCombined-method 的效果,就像在 ngrx-example-app 中展示的任何其他服务方法一样。
- 如果任一请求失败,服务将抛出一个错误,您可以在效果中捕获并处理该错误。
- 如果两个请求都成功,您将取回在
mergeMap 中定义的数据。如您所见,可以从两个请求-响应中返回合并数据。
场景 2
使用这种方法,您可以区分两个请求的结果,并在其中一个请求失败时做出不同的反应。
我建议将这两个调用分解为独立的操作,以便您可以独立地减少每个案例。
首先,服务现在有两个独立的方法(这里没什么特别的):
post.service.ts
@Injectable()
export class PostService {
private API_URL1 = 'http://your.api.com/resource1';
private API_URL2 = 'http://your.api.com/resource2';
constructor(private http: Http) { }
post1(formValues: { name: string }): Observable<{ id: number }> {
return this.http.post(this.API_URL1, formValues).map(res => res.json());
}
post2(receivedId: number, formValues: { age: number }): Observable<any> {
return this.http.post(this.API_URL2, {
id: receivedId,
age: formValues.age,
timestamp: new Date()
})
.map(res => res.json());
}
}
接下来为两个请求定义请求、成功和失败操作:
post.actions.ts
import { Action } from '@ngrx/store';
export const POST1_REQUEST = 'POST1_REQUEST';
export const POST1_SUCCESS = 'POST1_SUCCESS';
export const POST1_FAILURE = 'POST1_FAILURE';
export const POST2_REQUEST = 'POST2_REQUEST';
export const POST2_SUCCESS = 'POST2_SUCCESS';
export const POST2_FAILURE = 'POST2_FAILURE';
export class Post1RequestAction implements Action {
readonly type = POST1_REQUEST;
constructor(public payload: { name: string, age: number }) { }
}
export class Post1SuccessAction implements Action {
readonly type = POST1_SUCCESS;
constructor(public payload: { id: number }) { }
}
export class Post1FailureAction implements Action {
readonly type = POST1_FAILURE;
constructor(public error: any) { }
}
export class Post2RequestAction implements Action {
readonly type = POST2_REQUEST;
constructor(public payload: { id: number, name: string, age: number}) { }
}
export class Post2SuccessAction implements Action {
readonly type = POST2_SUCCESS;
constructor(public payload: any) { }
}
export class Post2FailureAction implements Action {
readonly type = POST2_FAILURE;
constructor(public error: any) { }
}
export type Actions
= Post1RequestAction
| Post1SuccessAction
| Post1FailureAction
| Post2RequestAction
| Post2SuccessAction
| Post2FailureAction
现在我们可以定义两个效果,它们将在请求动作被分派时运行,然后根据服务调用的结果分派成功或失败动作:
post.effects.ts
import { PostService } from '../services/post.service';
import * as post from '../actions/post';
@Injectable()
export class PostEffects {
@Effect()
post1$: Observable<Action> = this.actions$
.ofType(post.POST1_REQUEST)
.map(toPayload)
.switchMap(formValues => this.postService.post1(formValues)
.mergeMap(post1Result =>
Observable.from([
/*
* dispatch an action that signals that
* the first request was successful
*/
new post.Post1SuccessAction(post1Result),
/*
* dispatch an action that triggers the second effect
* as payload we deliver the id we received from the first call
* and any other values the second request needs
*/
new post.Post2RequestAction({
id: post1Result.id,
name: formValues.name,
age: formValues.age
})
])
)
.catch(err => Observable.of(new post.Post1FailureAction(err)))
);
@Effect()
post2$: Observable<Action> = this.actions$
/*
* this effect will only run if the first was successful
* since it depends on the id being returned from the first request
*/
.ofType(post.POST2_REQUEST)
.map(toPayload)
.switchMap(formValuesAndId =>
this.postService.post2(
/* we have access to the id of the first request */
formValuesAndId.id,
/* the rest of the form values we need for the second request */
{ age: formValuesAndId.age }
)
.map(post2Result => new post.Post2SuccessAction(post2Result))
.catch(err => Observable.of(new post.Post2FailureAction(err)))
);
constructor(private actions$: Actions, private postService: PostService) { }
}
注意mergeMap 与Observable.from([..]) 在第一个效果中的组合。它允许您调度可以减少(通过减速器)的Post1SuccessAction 以及将触发第二个效果运行的Post2RequestAction。如果第一个请求失败,第二个请求将不会运行,因为 Post2RequestAction 没有被分派。
如您所见,以这种方式设置操作和效果可让您独立于其他请求对失败的请求做出反应。
要开始第一个请求,您只需在提交表单时发送Post1RequestAction。比如this.store.dispatch(new post.Post1RequestAction({ name: 'Bob', age: 45 }))。