思考Angular1和Angular2的异步处理的不同之处

首先

我是@Quramy,对于Angular1,我有一定的了解,但是这是我第一次讨论Angular2,感觉像个Angular2的新手。

我今天想探讨一下Angular1和Angular2的HTTP服务API之间的区别,特别是它们对于异步处理的立场上的差异。

让我们来看一下Ajax实现代码的差异。

首先,让我们用Angular1和Angular2分别编写一段TypeScript代码来简单地通过GET方法获取如下所示的Pet资源。

export interface IPet {
    id: string;
    type: string;
    name: string;
}

首先从Angular1的HTTP开始。使用熟悉的$http服务。

/// <reference path="../typings/angularjs/angular.d.ts" />

class AppCtrl {
    pet: IPet;
    constructor(private $http: ng.IHttpService) {
        this.$http.get('/api/v1/pets/001.json')
        .then(resposnse => response.data as IPet)
        .then(pet => {
            this.pet = pet;
            console.log(this.pet);
            // :
        });
    }
}
angular.module('app').controller('AppCtrl', AppCtrl);

接下来是针对Angular2的情况。我们将使用angular2/http模块中包含的Http服务。

import {Component} from 'angular2/angular2';
import {Http, HTTP_PROVIDERS, Response} from 'angular2/http';

@Component({
    selector: 'ngcli-sample01-app',
    providers: [HTTP_PROVIDERS],
    templateUrl: ...
})
export class MyApp {
    pet: IPet;
    constructor(private http: Http) {
        this.http.get('/api/v1/pets/001.json')
        .map((response: Response) => response.json() as IPet)
        .subscribe(pet => {
            this.pet = pet;
            console.log(this.pet)
            // :
        });
    }
}

每个get方法都注册了以下两个回调函数,这种结构是相同的。 (请原谅,这只是一个示例,关于“通常应该创建服务,而不是直接从组件调用HTTP”的评论)

    • HTTP Response からbody部からJSON(IPet)を取り出す

 

    受けとったIPet型のオブジェクトをゴニョゴニョする(大概はコンポーネントのメンバ変数に代入されたりする)

在使用HTTP服务之前,导入模块和使用装饰器等准备工作可能有所不同,但是在应用程序代码中,以下是主要的区别。

    • Angular1の場合、 .then(…) でコールバック関数を登録

 

    Angular2の場合、 .map(…) と .subscribe(…) にコールバック関数を登録

在氛围中,.map是从响应中提取对象的过程,而.subscribe则类似于注册监听器,这一点我明白了。但在我首次接触Angular2的Http服务时,我想到的是:“为什么不再用then(…)了?map和subscribe是些什么东西?”

承诺还是可观察性,这是个问题。

在Angular1中使用$http服务执行Ajax后,返回的结果都是一个类型为ng.IPromise的返回值。实际上,它是通过$q服务生成的,但可以像ES6中规范化的Promise一样使用。

Angular2的Http服务返回的不是Promise,而是Observable类。
Observable类包含在核心模块angular2/angular2中,而不是angular2/http模块中。

查看该定义后,我们可以看出 Angular2 并非自己定义的,而是 ReactiveX/RxJS。

export declare class Observable<T> extends RxObservable<T> {
    lift<T, R>(operator: Operator<T, R>): Observable<T>;
}
import { Subject, Observable as RxObservable } from '@reactivex/rxjs/dist/cjs/Rx';

一旦到这个地步,已经不再只是关于HTTP或Ajax的讨论,而是涉及到所有异步处理的话题了。

终于把本篇文章的标题拿出来了。
对于我最初的疑问“为什么http.get(…).then消失了?”也就是“为什么Angular2将$q删除了?”的答案(虽然肤浅)可以说是“因为转向了ReactiveX/RxJS而不再使用Promise”。

然而,這不是一個答案。
如果你想了解在Angular2代碼中提到的 .map, .subscribe 的使用方法以及Observable的其他功能,你可以閱讀ReactiveX / RxJS或其源頭的RxJS主模組參考來理解。
(正如@ armorik83所指出的,RxJS存在兩個系統。Angular2使用帶有@ReactiveX的那一個。)

我真正想要理解的是为什么选择改变Angular2,即其原因。

作为针对这个问题的一个答案,在2015年10月的Angular Connect会议上,Ben Lesh进行了有关RxJS的深度讲解,他提到了Observable优于Promise的方面(建议从5分48秒开始观看)。

简单来说,说明的内容如下:

在现代Web应用程序中,应该处理以下异步处理:

DOM事件(0-N个值)
动画(可取消)
Ajax(1个值)
WebSockets(0-N个值)
服务器端事件(0-N个值)

与Promise的特点(这种简单的特点也可以说是Promise的优点)相对比,可以得出以下结论:

确保要么成功要么失败。一旦生成了Promise,就不会被取消。
一旦决定了成功或失败的结果,就是不变的。

在上述提到的异步处理中,只有Ajax可以由Promise来解决。
而可以将发生的事件视为流来处理的Observable可以适用于先前提到的任何异步处理。

看起来 RxJS 的开发者对 Promise 的评价好像不太好…
无论是 Promise 还是 Observable,它们都可以成为帮助开发者摆脱回调地狱的手段,但是事实上它们各自都有长短之处。

承诺是一种名为Future的设计模式,其根本目的是抽象化”调用处理 -> (必要时等待) -> 接收结果并继续下一个处理”的过程。
正因为如此,像最近从TypeScript 1.7开始可用的Async/Await这样的记法很适合编写包含串行异步代码的形式。
如果有人说”Promise可能会被多次解析,所以无法确定then中注册的回调会执行多少次”或者”Promise已被取消”之类的话,我根本无法想象如何能写出类似Async/Await的代码。

一方,Observable是源头的发布者/订阅者模型。
这一模型的最大特点是通过“将事件发生的源头(发布者)和监听者(订阅者)分离来实现松耦合性”。 Observable相当于订阅者,无论事件的发生源是Http.get(url)还是其他什么,它对此一无所知。
正是这种松耦合性,成为处理各种事件源的动力。

Angular = {1: “双向”, 2: “单向”};

最后,我想考虑一下Angular1和Angular2在数据绑定思想方面的差异与异步实现的关系。

根据 @shinsukeimai 先生在一昨日的Advent Calendar 上的说法,Angular2中的数据绑定方式变得更加灵活。
与Angular1最大的特点——双向数据绑定不同,现在默认情况下,采用了 [视图 -> 组件] 和 [组件 -> 视图] 这两种单向绑定的组合来实现。

另一方面,关于Observable(Angular2),就像之前提到的那样,“异步处理的发生源”和“结果处理”是独立的,所以我们可以用之前的类比来理解,它有两个单向存在,即[某个 -> 异步发生]和[某种结果 -> 后续处理]。

到了这一步,它变成了一个听过的故事。 是的,就是那个在Flux和Redux中被称为“一切都是单向处理”的东西。
从Promise到Observable的转变似乎也可以以单向处理为中心。

顺便提一下,双向绑定真的是一件坏事吗?
在Angular1中,双向数据绑定和Promise的”[调用 <-> 处理结果]的组合”确实可能无法解决像Web Socket那样的情况,正如Ben Lesh所说。
但是,通过遵循“将输入和输出始终作为一组处理”这个限制来编写代码,我们可以得到相当清晰的代码。

单向处理和事件与监听器的松散耦合性使得单独的组件可测试性较高,但也包含了容易陷入“不知道在哪里会发生什么”情况的风险。
Flux和Redux通过将“何时何地发生何事”纳入指南,试图避免这种风险。

最后一句

即使$q已经被删除,但由于Observable中存在诸如toPromise(…)等转换工具,所以我认为在Angular2中也有可能基于Promise来处理异步的应用程序,就像在Angular1中一样。

在Angular2中可以使用RxJS的丰富功能确实令人高兴,但同时也感觉到在设计时需要更加谨慎。尽管我在实际工作中对RxJS和FRP的范式都没有实践和设计经验,但我觉得通过Angular2来面对它们也是很有趣的事情。

今天的话题几乎没有涉及到实施,但是我希望将来既要在Angular2上尝试设计和实施双重面也要再次谈论这个主题。

明天是 @yuhiisk 先生。

请在中国本地语言中进行排列顺序

Angular2 Observables, Http, and separating services and components : Angular2 Httpの使い方がより詳細に紹介されてます

【翻訳】あなたが求めていたリアクティブプログラミング入門 : 程よい粒度でReactiveについて解説してくれてる

デザインパターン紹介 -GoF以外のパターンを紹介します- : そもそもFutureパターンって何だっけ?って話を結城先生が解説してくれてる

广告
将在 10 秒后关闭
bannerAds