如何让使用Zone.JS的Angular Universal等待AWS Lambda SDK调用完成
问题原因
Zone.js 默认仅对 @angular/common/http 依赖的 XHR 调用、Node.js 原生基础异步 API 做了补丁处理,aws-sdk 基于自定义 https Agent 发起的请求逻辑没有被默认纳入 Zone 的任务跟踪范围,因此 Angular Universal 判定应用稳定时不会等待这类请求完成,就会提前返回渲染结果。
解决方案
两种方案均可实现让 Universal 等待 aws-sdk 调用完成:
方案1:通过 NgZone 包裹异步逻辑(推荐,最简)
直接将你的 Lambda 调用逻辑包裹进 Angular 内置的 NgZone 执行上下文,Zone 会自动跟踪内部所有异步任务的状态,等待全部完成后再触发服务端渲染:
import { Injectable, NgZone } from '@angular/core'; import { ApolloLink, Observable } from '@apollo/client/core'; import { AuthService } from '@tma/core'; import AwsLambda from 'aws-sdk/clients/lambda'; import { print } from 'graphql'; import { Agent } from 'https'; import { first } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class ApiLinkLambda extends ApolloLink { client = new AwsLambda({ region: process.env.AWS_REGION, httpOptions: { agent: new Agent({ keepAlive: true, maxSockets: 50, rejectUnauthorized: true, }), }, }); // 注入NgZone constructor(private authService: AuthService, private ngZone: NgZone) { super(operation => { return new Observable(observer => { // 将所有异步逻辑包裹到NgZone执行上下文 this.ngZone.run(() => { this.authService.token$ .pipe(first()) .toPromise() .then(token => { return this.client .invoke({ FunctionName: 'example-graphqlServer', InvocationType: 'RequestResponse', Payload: JSON.stringify({ body: { query: print(operation.query), variables: operation.variables, }, headers: { Authorization: token, }, httpMethod: 'POST', path: '/graphql', requestContext: { requestTimeEpoch: Date.now(), }, }), }) .promise() .then(result => { const response = JSON.parse(result.Payload!.toString()); const body = JSON.parse(response.body); observer.next(body); observer.complete(); }) .catch(err => observer.error(err)); }) .catch(err => observer.error(err)); }); }); }); } }
方案2:手动注册宏任务(兼容性更好)
如果方案1不生效,可以手动向 Zone 注册宏任务,明确告知 Zone 有需要等待的异步任务执行,任务完成后手动触发结束标记即可。该方案和 Angular 服务端 HttpClient 的实现逻辑一致,完全可控:
super(operation => { return new Observable(observer => { // 手动注册需要等待的宏任务 const macroTask = Zone.current.scheduleMacroTask( 'aws-lambda-invoke', () => {}, {}, // 任务执行逻辑 () => { this.authService.token$ .pipe(first()) .toPromise() .then(token => this.client.invoke({/* 原有参数保持不变 */}).promise()) .then(result => { const response = JSON.parse(result.Payload!.toString()); const body = JSON.parse(response.body); observer.next(body); observer.complete(); // 标记任务完成 macroTask.invoke(); }) .catch(err => { observer.error(err); macroTask.invoke(); }); }, () => {} ); }); });
内容的提问来源于stack exchange,提问作者creamcheese
相关产品推荐
相关产品推荐

