From 79eddc255303f752b58ab12699353f4bab17d03c Mon Sep 17 00:00:00 2001 From: Zhicheng Wang Date: Wed, 25 May 2016 21:17:59 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=80=E5=8F=91=E6=8C=87=E5=8D=97-HTTP?= =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=AB=AF=20=E7=BF=BB=E8=AF=91=E5=AE=8C?= =?UTF-8?q?=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ts/latest/guide/server-communication.jade | 195 ++++++++++++++++++ 1 file changed, 195 insertions(+) diff --git a/public/docs/ts/latest/guide/server-communication.jade b/public/docs/ts/latest/guide/server-communication.jade index 63c6bfb3a6..6b7770550d 100644 --- a/public/docs/ts/latest/guide/server-communication.jade +++ b/public/docs/ts/latest/guide/server-communication.jade @@ -662,36 +662,60 @@ block hero-list-comp-add-hero :marked Back in the `HeroListComponent`, we see that *its* `addHero()` method subscribes to the observable returned by the *service's* `addHero()` method. When the data, arrive it pushes the new hero object into its `heroes` array for presentation to the user. + + 回到`HeroListComponent`,我们看到*该组件的*`addHero()`方法中订阅了这个由*服务中*的`addHero()`方法返回的可观察对象。 + 当有数据到来时,它就会把这个新的英雄对象追加(push)到`heroes`数组中,以展现给用户。 +makeExample('server-communication/ts/app/toh/hero-list.component.ts', 'addHero', 'app/toh/hero-list.component.ts (addHero)')(format=".") block promises h2#promises Fall back to Promises + h2#promises 退化为承诺(Promise) :marked Although the Angular `http` client API returns an `Observable` we can turn it into a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) if we prefer. It's easy to do and a promise-based version looks much like the observable-based version in simple cases. + + 虽然Angular的`http`客户端API返回的是`Observable`类型的对象,但我们也可以把它转成 + [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)。 + 这很容易,并且在简单的场景中,一个基于承诺(Promise)的版本看起来很像基于可观察对象(Observable)的版本。 .l-sub-section :marked While promises may be more familiar, observables have many advantages. Don't rush to promises until you give observables a chance. + + 可能“承诺”看起来更熟悉一些,但“可观察对象”有很多优越之处。 + 不要匆匆忙忙的就决定用“承诺”,你值得给“可观察对象”一次机会。 :marked Let's rewrite the `HeroService` using promises , highlighting just the parts that are different. + + 我们来使用承诺重写`HeroService`,要特别注意那些不同的部分。 +makeTabs( 'server-communication/ts/app/toh/hero.service.promise.ts,server-communication/ts/app/toh/hero.service.ts', 'methods, methods', 'app/toh/hero.service.promise.ts (promise-based), app/toh/hero.service.ts (observable-based)') :marked Converting from an observable to a promise is as simple as calling `toPromise(success, fail)`. + + 把可观察对象转变成承诺是很简单的,只要调用`toPromise(success, fail)`就行了。 We move the observable's `map` callback to the first *success* parameter and its `catch` callback to the second *fail* parameter and we're done! Or we can follow the promise `then.catch` pattern as we do in the second `addHero` example. + + 我们把可观察对象的`map`回调移到第一个*success*参数中,而它的`catch`回调移到第二个*fail*参数中,这就算完工了。 + 或者我们可以像第二个`addHero`例子中那样使用承诺的`then.catch`模式。 Our `errorHandler` forwards an error message as a failed promise instead of a failed Observable. + + 我们的`errorHandler`也改用了一个失败的承诺,而不再是失败的可观察对象。 The diagnostic *log to console* is just one more `then` in the promise chain. + + 把诊断信息*记录到控制台*也只是在承诺的处理链中多了一个`then`而已。 We have to adjust the calling component to expect a `Promise` instead of an `Observable`. + + 为了用`Observable`代替`Promise`,我们还得对调用方组件进行调整。 +makeTabs( 'server-communication/ts/app/toh/hero-list.component.promise.ts, server-communication/ts/app/toh/hero-list.component.ts', @@ -700,44 +724,77 @@ block promises :marked The only obvious difference is that we call `then` on the returned promise instead of `subscribe`. We give both methods the same functional arguments. + + 唯一一个比较明显的不同点是我们调用这个返回的承诺的`then`方法,而不再是`subscribe`。 + 而且我们给了这两个方法完全相同的调用参数。 .l-sub-section :marked The less obvious but critical difference is that these two methods return very different results! + + 细微却又关键的不同点是,这两个方法返回了非常不同的结果! The promise-based `then` returns another promise. We can keep chaining more `then` and `catch` calls, getting a new promise each time. + + 基于承诺的`then`返回了另一个承诺。我们可以链式调用多个`then`和`catch`方法,每次都返回一个新的承诺。 The `subscribe` method returns a `Subscription`. A `Subscription` is not another `Observable`. It's the end of the line for observables. We can't call `map` on it or call `subscribe` again. The `Subscription` object has a different purpose, signified by its primary method, `unsubscribe`. + + 但`subscribe`方法返回一个`Subscription`对象。但`Subscription`不是另一个`Observable`。 + 它是可观察对象的末端。我们不能在它上面调用`map`函数或再次调用`subscribe`函数。 + `Subscription`对象的设计目的是不同的,这从它的主方法`unsubscribe`就能看出来。 Learn more about observables to understand the implications and consequences of subscriptions. + + 请学习更多关于可观察对象的知识,来理解订阅的实现和效果。 h2#cors Cross-origin requests: Wikipedia example +h2#cors 跨域请求:Wikipedia范例 :marked We just learned how to make `XMLHttpRequests` using the !{_Angular_Http} service. This is the most common approach for server communication. It doesn't work in all scenarios. + + 我们刚刚学习了用!{_Angular_Http}服务发起`XMLHttpRequests`。 + 这是与服务器通讯时最常用的方法。 + 但它不适合所有场景。 For security reasons, web browsers block `XHR` calls to a remote server whose origin is different from the origin of the web page. The *origin* is the combination of URI scheme, hostname and port number. This is called the [Same-origin Policy](https://en.wikipedia.org/wiki/Same-origin_policy). + + 出于安全的考虑,Web浏览器会阻止到与当前页面不“同源”的远端服务器的`XHR`调用。 + 所谓*源*就是URI的协议(scheme)、主机名(host)和端口号(port)这几部分的组合。 + 这被称为[同源策略](https://en.wikipedia.org/wiki/Same-origin_policy)。 .l-sub-section :marked Modern browsers do allow `XHR` requests to servers from a different origin if the server supports the [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) protocol. If the server requires user credentials, we'll enable them in the [request headers](#headers). + + 在现代浏览器中,如果服务器支持[CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing)协议,那么也可以向不同源的服务器发起`XHR`请求。 + 如果服务器要请求用户凭证,我们就在[请求头](#headers)中启用它们。 :marked Some servers do not support CORS but do support an older, read-only alternative called [JSONP](https://en.wikipedia.org/wiki/JSONP). Wikipedia is one such server. + + 有些服务器不支持CORS,但支持一种老的、只读的(译注:即仅支持GET)替代协议,那就是[JSONP](https://en.wikipedia.org/wiki/JSONP)。 + Wikipedia就是一个这样的服务器。 .l-sub-section :marked This [StackOverflow answer](http://stackoverflow.com/questions/2067472/what-is-jsonp-all-about/2067584#2067584) covers many details of JSONP. + + 这个[StackOverflow上的答案](http://stackoverflow.com/questions/2067472/what-is-jsonp-all-about/2067584#2067584)覆盖了关于JSONP的很多细节。 :marked ### Search wikipedia + ### 搜索Wikipedia Let's build a simple search that shows suggestions from wikipedia as we type in a text box. + + 我们来构建一个简单的搜索程序,当我们在文本框中输入时,它会从Wikipedia中获取并显示建议的词汇列表。 figure.image-display img(src='/resources/images/devguide/server-communication/wiki-1.gif' alt="Wikipedia search app (v.1)" width="250") @@ -747,162 +804,276 @@ block wikipedia-jsonp+ Wikipedia offers a modern `CORS` API and a legacy `JSONP` search API. Let's use the latter for this example. The Angular `Jsonp` service both extends the `#{_Http}` service for JSONP and restricts us to `GET` requests. All other HTTP methods throw an error because JSONP is a read-only facility. + + Wikipedia提供了一个现代的`CORS` API和一个传统的`JSONP`搜索API。在这个例子中,我们使用后者。 + Angular的`Jsonp`服务既通过JSONP扩展了`#{_Http}`服务,又限制了我们只能用`GET`请求。 + 尝试调用所有其它HTTP方法都将抛出一个错误,这是因为JSONP是只读的。 As always, we wrap our interaction with an Angular data access client service inside a dedicated service, here called `WikipediaService`. + + 像往常一样,我们把和Angular数据访问服务进行交互的代码全都封装在一个专门的服务中。我们称之为`WikipediaService`。 +makeExample('server-communication/ts/app/wiki/wikipedia.service.ts',null,'app/wiki/wikipedia.service.ts') :marked The constructor expects Angular to inject its `jsonp` service. We register that service with `JSONP_PROVIDERS` in the [component below](#wikicomponent) that calls our `WikipediaService`. + + 这个构造函数期望Angular给它注入一个`jsonp`服务。 + 我们在[下面这个组件](#wikicomponent)(它会调用`WikipediaService`服务)中,用`JSONP_PROVIDERS`注册了这个服务。 :marked ### Search parameters + ### 搜索参数 The [Wikipedia 'opensearch' API](https://www.mediawiki.org/wiki/API:Opensearch) expects four parameters (key/value pairs) to arrive in the request URL's query string. The keys are `search`, `action`, `format`, and `callback`. The value of the `search` key is the user-supplied search term to find in Wikipedia. The other three are the fixed values "opensearch", "json", and "JSONP_CALLBACK" respectively. + + [Wikipedia 的 'opensearch' API](https://www.mediawiki.org/wiki/API:Opensearch)期待在所请求的URL中带四个查询参数(键/值对格式)。 + 这些键(key)分别是`search`、`action`、`format`和`callback`。 + `search`的值是一个用户提供的打算在Wikipedia中查找的关键字。 + 另外三个参数是固定值,分别是"opensearch"、"json"和"JSONP_CALLBACK"。 .l-sub-section :marked The `JSONP` technique requires that we pass a callback function name to the server in the query string: `callback=JSONP_CALLBACK`. The server uses that name to build a JavaScript wrapper function in its response which Angular ultimately calls to extract the data. All of this happens under the hood. + + `JSONP`技术需要我们通过查询参数传给服务器一个回调函数的名字:`callback=JSONP_CALLBACK`。 + 服务器使用这个名字在它的响应体中构建一个JavaScript包装函数,Angular最终会调用这个包装函数来提取出数据。 + 这些都是Angular在背后默默完成的,你不会感受到它。 :marked If we're looking for articles with the word "Angular", we could construct the query string by hand and call `jsonp` like this: + + 如果我们要找那些含有关键字“Angular”的文档,我们可以先手工构造出查询字符串,并像这样调用`jsonp`: +makeExample('server-communication/ts/app/wiki/wikipedia.service.1.ts','query-string')(format='.') :marked In more parameterized examples we might prefer to build the query string with the Angular `URLSearchParams` helper as shown here: + + 在更为参数化的例子中,我们会首选Angular的`URLSearchParams`辅助类来构建查询字符串,就像这样: +makeExample('server-communication/ts/app/wiki/wikipedia.service.ts','search-parameters','app/wiki/wikipedia.service.ts (search parameters)')(format=".") :marked This time we call `jsonp` with *two* arguments: the `wikiUrl` and an options object whose `search` property is the `params` object. + + 这次我们使用了*两个*参数来调用`jsonp`:`wikiUrl`和一个配置对象,配置对象的`search`属性是刚构建的这个`params`对象。 +makeExample('server-communication/ts/app/wiki/wikipedia.service.ts','call-jsonp','app/wiki/wikipedia.service.ts (call jsonp)')(format=".") :marked `Jsonp` flattens the `params` object into the same query string we saw earlier before putting the request on the wire. + + `Jsonp`把`params`对象平面化为一个查询字符串,而这个查询字符串和以前我们直接放在请求中的那个是一样的。 :marked ### The WikiComponent + ### WikiComponent组件 Now that we have a service that can query the Wikipedia API, we turn to the component that takes user input and displays search results. + + 现在,我们有了一个可用于查询Wikpedia API的服务, + 我们重新回到组件中,接收用户输入,并显示搜索结果。 +makeExample('server-communication/ts/app/wiki/wiki.component.ts', null, 'app/wiki/wiki.component.ts') :marked The `providers` array in the component metadata specifies the Angular `JSONP_PROVIDERS` collection that supports the `Jsonp` service. We register that collection at the component level to make `Jsonp` injectable in the `WikipediaService`. + + 该组件元数据中的`providers`数组指定了Angular的`JSONP_PROVIDERS`集合,以支持`Jsonp`服务。 + 我们把这个集合注册在组件级,以便`Jsonp`可以被注入进`WikipediaService`中。 The component presents an `` element *search box* to gather search terms from the user. and calls a `search(term)` method after each `keyup` event. + + 该组件把一个``元素作为*搜索框*作为从用户那里手机搜索关键字的入口。 + 并在每次`keyup`事件被触发时,调用`search(term)`方法。 The `search(term)` method delegates to our `WikipediaService` which returns an observable array of string results (`Observable ## More fun with Observables + ## Observable的更多乐趣 We can address these problems and improve our app with the help of some nifty observable operators. + + 借助一些漂亮的可观察对象操作函数,我们可以解决这些问题,并提升我们的应用程序。 We could make our changes to the `WikipediaService`. But we sense that our concerns are driven by the user experience so we update the component class instead. + + 我们本可以把这些改动合并进`WikipediaService`中,但别忘了我们的关注点应该是由用户体验驱动的,所以,还是把它放到组件类中吧。 +makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', null, 'app/wiki/wiki-smart.component.ts') :marked We made no changes to the template or metadata, confining them all to the component class. Let's review those changes. + + 我们没有修改模板或元数据,只改了组件类。 + 我们来回顾一下这些改动。 ### Create a stream of search terms + ### 创建一个有搜索关键字组成的“流(Stream)” We're binding to the search box `keyup` event and calling the component's `search` method after each keystroke. + + 我们正在绑定到搜索框的`keyup`事件,并且在每次按键之后调用组件的`search`方法。 We turn these events into an observable stream of search terms using a `Subject` which we import from the RxJS observable library: + + 我们借助`Subject`类把这些事件转变成一个由搜索关键字组成的可观察的“流”对象。`Subject`是我们从RxJS库中导入的: +makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'import-subject') :marked Each search term is a string, so we create a new `Subject` of type `string` called `searchTermStream`. After every keystroke, the `search` method adds the search box value to that stream via the subject's `next` method. + + 每个搜索关键字都是一个字符串,所以我们创建了一个新的`string`类型的`Subject`,并把它称为`searchTermStream`。 + 在每次按键之后,`search`方法都会通过`Subject`的`next`方法把搜索框的值添加到流中。 +makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'subject')(format='.') :marked ### Listen for search terms + ### 监听搜索关键字 Earlier, we passed each search term directly to the service and bound the template to the service results. Now we listen to the *stream of terms*, manipulating the stream before it reaches the `WikipediaService`. + + 以前,我们每次都把搜索关键字直接传给服务,并且把模板绑定到服务返回的结果。 + 而现在我们在监听*关键字组成的流*,并在把它传给`WikipediaService`之前操作这个流。 +makeExample('server-communication/ts/app/wiki/wiki-smart.component.ts', 'observable-operators')(format='.') :marked We wait for the user to stop typing for at least 300 milliseconds ([debounceTime](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/debounce.md)). Only changed search values make it through to the service ([distinctUntilChanged](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/distinctuntilchanged.md)). + + 我们先等待用户停止输入至少300毫秒 + ([debounceTime](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/debounce.md))。 + 只有当搜索关键字变化的时候,才把它传给服务 + ([distinctUntilChanged](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/distinctuntilchanged.md))。 The `WikipediaService` returns a separate observable of string arrays (`Observable`) for each request. We could have multiple requests *in flight*, all awaiting the server's reply, which means multiple *observables-of-strings* could arrive at any moment in any order. + + `WikipediaService`服务为每个请求返回一个独立的可观察的字符串数组(`Observable`)。 + 我们可以同时有多个*发送中*的请求,它们都在等服务器的回复, + 这意味着多个*可观察的字符串数组*有可能在任何时刻以任何顺序抵达。 The [switchMap](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/flatmaplatest.md) (formerly known as `flatMapLatest`) returns a new observable that combines these `WikipediaService` observables, re-arranges them in their original request order, and delivers to subscribers only the most recent search results. + + [switchMap](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/flatmaplatest.md)(常被称为`flatMapLatest`) + 返回一个新的可观察对象,它组合了所有这些“可观察的字符串数组”,重新按照它们的原始请求顺序进行排列,然后把最近的一个搜索结果交付给调用者。 The displayed list of search results stays in sync with the user's sequence of search terms. + + 于是,最终显示的搜索结果列表和用户输入的搜索关键字在顺序上保持了一致。 .l-sub-section :marked We added the `debounceTime`, `distinctUntilChanged`, and `switchMap` operators to the RxJS `Observable` class in `rxjs-operators` as [described above](#rxjs) + + 在[前面提过的](#rxjs)`rxjs-operators`文件中,我们把`debounceTime`、`distinctUntilChanged`和`switchMap`操作函数加到了RxJS的`Observable`类中。 a#in-mem-web-api .l-main-section :marked ## Appendix: Tour of Heroes in-memory server + ## 附录:《英雄指南》的内存(in-memory)服务器 If we only cared to retrieve data, we could tell Angular to get the heroes from a `heroes.json` file like this one: + + 如果我们只关心获取到的数据,我们可以告诉Angular从一个从一个`heroes.json`文件中获取英雄列表,就像这样: +makeJson('server-communication/ts/app/heroes.json', null, 'app/heroes.json')(format=".") .l-sub-section :marked We wrap the heroes array in an object with a `data` property for the same reason that a data server does: to mitigate the [security risk](http://stackoverflow.com/questions/3503102/what-are-top-level-json-arrays-and-why-are-they-a-security-risk) posed by top-level JSON arrays. + + 我们把英雄数组包装在一个带有`data`属性的对象中,就像一个数据服务器所做的那样: + 用于缓解由顶层的JSON数组带来的[安全风险](http://stackoverflow.com/questions/3503102/what-are-top-level-json-arrays-and-why-are-they-a-security-risk)。 :marked We'd set the endpoint to the JSON file like this: + + 我们要像这样把端点设置为这个JSON文件: +makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint-json')(format=".") - var _a_ca_class_with = _docsFor === 'ts' ? 'a custom application class with' : '' @@ -911,6 +1082,10 @@ a#in-mem-web-api But we want to *save* data too. We can't save changes to a JSON file. We need a web API server. We didn't want the hassle of setting up and maintaining a real server for this chapter. So we turned to an *in-memory web API simulator* instead. + + 这在*“获取”英雄数据*的场景下确实能工作,但我们还想*保存*数据。我们不能把这些改动保存到JSON文件中,我们需要一个Web API服务器。 + 在本章中,我们不想惹上配置和维护真实服务器的那些麻烦事。 + 所以,我们转而使用一种*内存Web API仿真器*代替它。 .l-sub-section :marked @@ -918,29 +1093,49 @@ a#in-mem-web-api It's an optional service in its own `angular2-in-memory-web-api` library that we installed with npm (see `package.json`) and registered for module loading by SystemJS (see `systemjs.config.js`) + + 内存Web API不是Angular内核的一部分。 + 它是一个可选的服务,来自独立的`angular2-in-memory-web-api`库。我们可以通过npm(参见`package.json`)来安装它, + 并且通过SystemJS(参见`systemjs.config.js`)把它注册进模块加载器。 :marked The in-memory web API gets its data from #{_a_ca_class_with} a `createDb()` method that returns a map whose keys are collection names and whose values are #{_array}s of objects in those collections. + 内存Web API从一个带有`createDb()`方法的自定义类中获取数据,并且返回一个map,它的主键(key)是一组名字,而值(value)是一组与之对应的对象数组。 + Here's the class we created for this sample based on the JSON data: + + 这里是与范例中那个基于JSON的数据源完成相同功能的类: +makeExample('server-communication/ts/app/hero-data.ts', null, 'app/hero-data.ts')(format=".") :marked Ensure that the `HeroService` endpoint refers to the web API: + + 确保`HeroService`的端点指向了这个Web API: +makeExample('server-communication/ts/app/toh/hero.service.ts', 'endpoint')(format=".") :marked Finally, we need to redirect client HTTP requests to the in-memory web API. + + 最后,我们需要把来自HTTP客户端的请求重定向到这个内存Web API。 block redirect-to-web-api :marked This redirection is easy to configure because Angular's `http` service delegates the client/server communication tasks to a helper service called the `XHRBackend`. + + 这次重定向非常易于配置,这是因为Angular的`http`服务把客户端/服务器通讯的工作委托给了一个叫做`XHRBackend`的辅助服务。 To enable our server simulation, we replace the default `XHRBackend` service with the in-memory web API service using standard Angular provider registration techniques. We initialize the in-memory web API with *seed data* from the mock hero dataset at the same time. + + 要想启用我们的服务模拟器,我们通过Angular标准的“供应商注册”技术,把默认的`XHRBackend`服务替换为了这个内存Web API服务。 + 同时,我们使用来自模拟的英雄数据集的*种子数据*初始化了这个内存Web API。 p Here is the revised (and final) version of the #[code #[+adjExPath('app/main.ts')]] demonstrating these steps. + +p 下面是修改过的(也是最终的)#[code #[+adjExPath('app/main.ts')]]版本,用于演示这些步骤。 +makeExample('server-communication/ts/app/main.ts', 'final', 'app/main.ts (final)')(format=".") p See the full source code in the #[+liveExampleLink2()]. +p 要想查看完整的源代码,参见#[+liveExampleLink2('浏览器中运行在线版')]。