使用 RxJs Observable 来避免 Angular 应用中的 Promise 使用

简介: 使用 RxJs Observable 来避免 Angular 应用中的 Promise 使用

我们通过一个具体的例子来阐述。

考虑您正在构建一个搜索输入掩码,该掩码应在您键入时立即显示结果。

如果您曾经构建过这样的东西,那么您可能会意识到该任务带来的挑战。

  1. 不要在每次击键时都点击搜索端点

将搜索端点视为您按请求付费。不管它是不是你自己的硬件。我们不应该比需要的更频繁地敲击搜索端点。基本上我们只想在用户停止输入后点击它,而不是每次击键时点击它。

  1. 不要在后续请求中使用相同的查询参数命中搜索端点

假设您键入 foo,停止,键入另一个 o,然后立即退格并返回到 foo。这应该只是一个带有 foo 一词的请求,而不是两个,即使我们在搜索框中有 foo 后从技术上讲停止了两次。

3.处理乱序响应

当我们同时有多个请求进行中时,我们必须考虑它们以意外顺序返回的情况。考虑我们首先键入 computer,停止,请求发出,我们键入 car,停止,请求发出。现在我们有两个正在进行的请求。不幸的是,在为 car 携带结果的请求之后,为 computer 携带结果的请求又回来了。这可能是因为它们由不同的服务器提供服务。如果我们不正确处理此类情况,我们最终可能会显示 computer 的结果,而搜索框会显示 car.


我们将使用免费和开放的维基百科 API 来编写一个小演示。


为简单起见,我们的演示将只包含两个文件:app.ts 和 wikipedia-service.ts。 不过,在现实世界中,我们很可能会将事情进一步拆分。


让我们从一个基于 Promise 的实现开始,它不处理任何描述的边缘情况。


这就是我们的 WikipediaService 的样子。

使用了 jsonp 这个 Angular HTTP 服务:

上图将来自 angular/http 库中的 jsonp 返回的对象,使用 toPromise 方法转换成了 promise.

简单地说,我们正在注入 Jsonp 服务,以使用给定的搜索词针对维基百科 API 发出 GET 请求。 请注意,我们调用 toPromise 是为了从 Observable<Response> 到 Promise<Response>。 通过 then-chaining 我们最终得到一个 Promise<Array<string>> 作为我们搜索方法的返回类型。


到目前为止一切顺利,让我们看看保存我们的 App 组件的 app.ts 文件。看一下 wiki service 如何被消费的:

这里也没什么惊喜。 我们注入我们的 WikipediaService 并通过搜索方法向模板公开它的功能。 模板简单地绑定到 keyup 并调用 search(term.value) 利用 Angular 的很棒的模板引用功能。


我们解开 WikipediaService 的搜索方法返回的 Promise 的结果,并将其作为一个简单的字符串数组公开给模板,这样我们就可以让 *ngFor 循环遍历它并为我们构建一个列表。


不幸的是,这个实现没有解决我们想要处理的任何所描述的边缘情况。 让我们重构我们的代码,使其符合预期的行为。


让我们更改我们的代码,不要在每次击键时敲击端点,而是仅在用户停止输入 400 毫秒时发送请求。 这就是 Observables 真正闪耀的地方。 Reactive Extensions (Rx) 提供了广泛的运算符,让我们可以改变 Observables 的行为并创建具有所需语义的新 Observables。


为了揭示这样的超能力,我们首先需要获得一个 Observable<string> ,它携带用户输入的搜索词。 我们可以利用 Angular 的 formControl 指令,而不是手动绑定到 keyup 事件。 要使用此指令,我们首先需要将 ReactiveFormsModule 导入到我们的应用程序模块中。

导入后,我们可以在模板中使用 formControl 并将其设置为名称“term”。

<input type="text" [formControl]="term"/>

在我们的组件中,我们从@angular/form 创建了一个 FormControl 的实例,并将其公开为组件上名称 term 下的一个字段。


在幕后,术语会自动公开一个 Observable<string> 作为我们可以订阅的属性 valueChanges。 现在我们有了一个 Observable<string>,驯服用户输入就像在我们的 Observable 上调用 debounceTime(400) 一样简单。 这将返回一个新的 Observable<string>,它只会在 400 毫秒内没有新值出现时才发出新值。


这个新的对象会在我们期望的时间间隔之后,才会发生新值。至于如何控制时间间隔,对前端开发人员来说是一个黑盒子。

export class App {
  items: Array<string>;
  term = new FormControl();
  constructor(private wikipediaService: WikipediaService) {
    this.term.valueChanges
             .debounceTime(400)
             .subscribe(term => this.wikipediaService.search(term).then(items => this.items = items));
  }
}

正如我们所说,对我们的应用程序已经显示结果的搜索词发出另一个请求将是一种资源浪费。幸运的是,Rx 简化了许多几乎不需要提及的操作。为了实现所需的行为,我们所要做的就是在我们调用 debounceTime(400) 之后立即调用 distinctUntilChanged 运算符。同样,我们将返回一个 Observable<string> ,但它忽略了与前一个相同的值。


处理无序响应


处理无序响应可能是一项棘手的任务。基本上,我们需要一种方法来表示,一旦我们发出新请求,我们就不再对之前进行中的请求的结果感兴趣。换句话说:一旦我们开始一个新的请求,就取消所有先前的请求。正如我在开头简要提到的,Observables 是一次性的,这意味着我们可以取消订阅它们。


这是我们想要更改 WikipediaService 以返回 Observable<Array<string>> 而不是 Promise<Array<string>> 的地方。这就像删除 toPromise 并使用 map 而不是 then 一样简单。

search (term: string) {
  var search = new URLSearchParams()
  search.set('action', 'opensearch');
  search.set('search', term);
  search.set('format', 'json');
  return this.jsonp
              .get('http://en.wikipedia.org/w/api.php?callback=JSONP_CALLBACK', { search })
              .map((response) => response.json()[1]);
}

现在我们的 WikipediaSerice 返回一个 Observable 而不是 Promise,我们只需要在我们的 App 组件中将 then 替换为 subscribe 。

this.term.valueChanges
           .debounceTime(400)
           .distinctUntilChanged()
           .subscribe(term => this.wikipediaService.search(term).subscribe(items => this.items = items));

但是现在我们有两个 subscribe 调用。 这是不必要的冗长,通常是需要代码重构的标志。 好消息是,现在搜索返回一个 Observable<Array>,我们可以简单地使用 flatMap 通过组合 Observables 来将 Observable 投影到所需的 Observable<Array> 中。

this.term.valueChanges
         .debounceTime(400)
         .distinctUntilChanged()
         .flatMap(term => this.wikipediaService.search(term))
         .subscribe(items => this.items = items);

你可能想知道 flatMap 是做什么的,为什么我们不能在这里使用 map。


答案很简单。 map 操作符需要一个函数,它接受一个值 T 并返回一个值 U。例如一个接受一个字符串并返回一个数字的函数。 因此,当您使用 map 时,您会从 Observable<T> 获得一个 Observable<U>。 但是,我们的搜索方法本身会生成一个 Observable。 因此,来自我们在 distinctUntilChanged 之后的 Observable,map 会将我们带到 Observable<Observable<Array>。 这不是我们想要的。


另一方面, flatMap 运算符需要一个函数,它接受一个 T 并返回一个 Observable<U> 并为我们生成一个 Observable<U>。


注意:这并不完全正确,但它有助于简化。


这完全符合我们的情况。 我们有一个 Observable<string>,然后使用一个函数调用 flatMap,该函数接受一个字符串并返回一个 Observable<Array<string>>。


现在我们已经掌握了语义,还有一个小技巧可以用来节省一些打字的时间。 我们可以让 Angular 直接在模板中为我们解包,而不是手动订阅 Observable。 我们要做的就是在我们的模板中使用 AsyncPipe 并公开 Observable<Array<string>> 而不是 Array<string>。

@Component({
  selector: 'my-app',
  template: `
    <div>
      <h2>Wikipedia Search</h2>
      <input type="text" [formControl]="term"/>
      <ul>
        <li *ngFor="let item of items | async">{{item}}</li>
      </ul>
    </div>
  `
})
export class App {
  items: Observable<Array<string>>;
  term = new FormControl();
  constructor(private wikipediaService: WikipediaService) {
    this.items = this.term.valueChanges
                 .debounceTime(400)
                 .distinctUntilChanged()
                 .switchMap(term => this.wikipediaService.search(term));
  }
}


相关文章
|
2月前
|
缓存 JavaScript 前端开发
Angular 应用打包和部署
Angular 应用打包和部署
167 58
|
9天前
|
缓存 监控 JavaScript
Angular 应用打包和部署
【10月更文挑战第16天】Angular 应用的打包和部署是一个综合性的过程,需要考虑多个方面的因素。通过合理的打包和部署策略,可以确保应用在生产环境中稳定运行,并为用户提供良好的体验。你需要根据实际情况选择合适的部署方式,并不断优化和改进部署流程,以适应业务的发展和变化。
|
6天前
|
缓存 前端开发 JavaScript
前端serverless探索之组件单独部署时,利用rxjs实现业务状态与vue-react-angular等框架的响应式状态映射
本文深入探讨了如何将RxJS与Vue、React、Angular三大前端框架进行集成,通过抽象出辅助方法`useRx`和`pushPipe`,实现跨框架的状态管理。具体介绍了各框架的响应式机制,展示了如何将RxJS的Observable对象转化为框架的响应式数据,并通过示例代码演示了使用方法。此外,还讨论了全局状态源与WebComponent的部署优化,以及一些实践中的改进点。这些方法不仅简化了异步编程,还提升了代码的可读性和可维护性。
|
3月前
|
应用服务中间件 Java Maven
掌控视图的力量!深入解析 JSF 视图管理,揭秘视图生命周期的秘密,让你的应用更高效!
【8月更文挑战第31天】JavaServer Faces (JSF) 是一种强大的框架,用于管理 Web 应用程序的视图。本文通过具体案例介绍 JSF 视图管理的基础知识,包括创建、管理和销毁视图的过程。首先,在 Eclipse 中创建一个新 JSF 项目,并配置 Maven 依赖。接着,在 `WEB-INF` 目录下配置 `web.xml` 文件,设置 JSF servlet。
49 0
|
3月前
|
Java Spring
🔥JSF 与 Spring 强强联手:打造高效、灵活的 Web 应用新标杆!💪 你还不知道吗?
【8月更文挑战第31天】JavaServer Faces(JSF)与 Spring 框架是常用的 Java Web 技术。本文介绍如何整合两者,发挥各自优势,构建高效灵活的 Web 应用。首先通过 `web.xml` 和 `ContextLoaderListener` 配置 Spring 上下文,在 `applicationContext.xml` 定义 Bean。接着使用 `@Autowired` 将 Spring 管理的 Bean 注入到 JSF 管理的 Bean 中。
57 0
|
3月前
|
开发者 Java 开发框架
JSF与EJB,打造企业级应用的神器!让你的Web应用更加稳定、高效!
【8月更文挑战第31天】在现代企业级应用开发中,JSF(JavaServer Faces)与EJB(Enterprise JavaBeans)是两大核心技术。JSF作为一款基于Java的Web应用框架,以其丰富的UI组件和表单处理功能著称;EJB则专注于提供分布式事务处理及远程调用等企业级服务。两者的结合为企业应用带来了高效便捷的开发模式。下文将通过一个简单的示例展示如何利用JSF进行用户信息的输入与保存,并借助EJB实现相关业务逻辑。尽管这一组合具有明显优势,但在实际应用中还需考虑其局限性并作出合理选择。
51 0
|
3月前
|
开发者 安全 SQL
JSF安全卫士:打造铜墙铁壁,抵御Web攻击的钢铁防线!
【8月更文挑战第31天】在构建Web应用时,安全性至关重要。JavaServer Faces (JSF)作为流行的Java Web框架,需防范如XSS、CSRF及SQL注入等攻击。本文详细介绍了如何在JSF应用中实施安全措施,包括严格验证用户输入、使用安全编码实践、实施内容安全策略(CSP)及使用CSRF tokens等。通过示例代码和最佳实践,帮助开发者构建更安全的应用,保护用户数据和系统资源。
50 0
|
3月前
|
容器 iOS开发 Linux
震惊!Uno Platform 响应式 UI 构建秘籍大公开!从布局容器到自适应设计,带你轻松打造跨平台完美界面
【8月更文挑战第31天】Uno Platform 是一款强大的跨平台应用开发框架,支持 Web、桌面(Windows、macOS、Linux)及移动(iOS、Android)等平台,仅需单一代码库。本文分享了四个构建响应式用户界面的最佳实践:利用布局容器(如 Grid)适配不同屏幕尺寸;采用自适应布局调整 UI;使用媒体查询定制样式;遵循响应式设计原则确保 UI 元素自适应调整。通过这些方法,开发者可以为用户提供一致且优秀的多设备体验。
102 0
|
3月前
|
开发者 Windows Android开发
跨平台开发新选择:揭秘Uno Platform与.NET MAUI优劣对比,帮你找到最适合的框架,告别选择困难症!
【8月更文挑战第31天】本文对比了备受关注的跨平台开发框架Uno Platform与.NET MAUI的特点、优势及适用场景。Uno Platform基于WebAssembly和WebGL技术,支持Windows、iOS、Android及Web平台,而.NET MAUI由微软推出,旨在统一多种UI框架,支持Windows、iOS和Android。两者均采用C#和XAML进行开发,但在性能、平台支持及社区生态方面存在差异。Uno Platform在Web应用方面表现出色,但性能略逊于原生应用;.NET MAUI则接近原生性能,但不支持Web平台。开发者应根据具体需求选择合适的框架。
99 0
|
3月前
|
iOS开发 Android开发 MacOS
从零到全能开发者:解锁Uno Platform,一键跨越多平台应用开发的神奇之旅,让你的代码飞遍Windows、iOS、Android、macOS及Web,技术小白也能秒变跨平台大神!
【8月更文挑战第31天】从零开始,踏上使用Uno Platform开发跨平台应用的旅程。只需编写一次代码,即可轻松部署到Windows、iOS、macOS、Android及Web(通过WASM)等多个平台。Uno Platform为.NET生态带来前所未有的灵活性和效率,简化跨平台开发。首先确保安装了Visual Studio或VS Code及.NET SDK,然后选择合适的项目模板创建新项目。项目结构类似传统.NET MAUI或WPF项目,包含核心NuGet包。通过简单的按钮示例,你可以快速上手并构建应用。Uno Platform让你的技术探索之旅充满无限可能。
56 0