使用 RxJS timeout 操作符给 Angular SSR 服务器端渲染模式下的 HTTP 请求添加超时机制

Jerry Wang 发表于 2022/11/27 09:55:45 2022/11/27
【摘要】 使用 Angular Universal 进行服务器端渲染,最常见的一个问题就是,用户在网站上打开一个页面并看到一个白屏。翻译成 Web 应用领域的术语来说,就是首字节时间(Time to First Byte, 简称 TTFB) 过大。TTFB 是指从浏览器请求页面,到它从服务器接收到第一个信息字节之间的时间。在这种情况下,浏览器确实想从服务器接收响应,但请求以超时结束。

Angular Universal 是一个开源项目,扩展了 @angular/platform-server 的功能。 该项目使 Angular 中的服务器端渲染成为可能。

为了在服务器上渲染,Angular 使用 node.js 的 DOM 实现——domino. 对于每个 GET 请求,domino 都会创建一个类似的 Browser Document 对象。 在该对象上下文中,Angular 初始化应用程序,然后向后端发出请求,执行各种异步任务,并将任何来自组件的更改检测应用到 DOM,同时仍在 node.js 环境中运行。渲染引擎随后将 DOM 序列化为字符串并将字符串提供给服务器。服务器将此 HTML 作为对 GET 请求的响应发送。 服务器上的 Angular 应用程序在渲染后被销毁。

使用 Angular Universal 进行服务器端渲染,最常见的一个问题就是,用户在网站上打开一个页面并看到一个白屏。翻译成 Web 应用领域的术语来说,就是首字节时间(Time to First Byte, 简称 TTFB) 过大。TTFB 是指从浏览器请求页面,到它从服务器接收到第一个信息字节之间的时间。在这种情况下,浏览器确实想从服务器接收响应,但请求以超时结束。

在分析这个性能问题之前,我们有必要了解 Zone.js 和 ApplicationRef.

Zone.js 是一个允许开发人员跟踪异步操作的工具。在它的帮助下,Angular 创建了自己的 Zone 并在其中启动应用程序。在 Angular 区域中的每个异步操作结束时,都会触发更改检测。

ApplicationRef 是对正在运行的应用程序(文档)的引用。在这个类的所有功能中,我们对 ApplicationRef.isStable 属性感兴趣。它是一个发出布尔值的 Observable。当 Angular 区域中没有异步任务正在运行时,isStable 为 true,当有任何异步任务时,isStable 为 false.

因此,应用程序的稳定性,取决于 Angular 区域中异步任务的存在与否。

当应用程序的 ApplicationRef.isStable 第一次变为 true 时, Angular 会渲染当前状态的应用程序,并清理平台资源。

我们现在可以假设用户正在尝试打开一个无法达到稳定状态的应用程序。 setInterval、rxjs.interval 或在 Angular 区域中运行的任何其他递归的异步操作,以及 HTTP 请求,都会阻止 Angular 应用进入稳定状态。

我们可以使用 rxjs 的 timeout 操作符,强制使得一个长时间运行的 HTTP 请求超时。

import { timeout, catchError } from 'rxjs/operators';
import { of } from 'rxjs/observable/of';

http.get('<https://example.com>')
  .pipe(
    timeout(2000),
    catchError(e => of(null))
  ).subscribe()

上面例子的逻辑是,如果 HTTP 请求两秒内,没有从服务器端接收到响应,则进入 catchError 错误处理模块内部。

这个解决方案的缺点是,对于每个 HTTP 请求,我们都需要手动为其添加 timeout 操作符。

一种更加优雅的实现是,使用开发包 @ngx-ssr/timeout 里的 NgxSsrTimeoutModule.

如果模块被导入 AppServerModule,那么 HTTP 请求超时将只对服务器端渲染环境起作用。

import { NgModule } from '@angular/core';
import {
  ServerModule,
} from '@angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
import { NgxSsrTimeoutModule } from '@ngx-ssr/timeout';

@NgModule({
  imports: [
    AppModule,
    ServerModule,
    NgxSsrTimeoutModule.forRoot({ timeout: 500 }),
  ],
  bootstrap: [AppComponent],
})
export class AppServerModule {}
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区),文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:cloudbbs@huaweicloud.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。