第十一课:获取数据,Observable和Promise

虽然很多移动应用是完全自容的(计算器,音板,待办列表,照片应用,手电筒应用),但很多应用靠着从外部源拉取数据。Facebook需要为新闻种子拉取数据,Instagram的最新图片,天气原因的最新天气预报等等。
这个部分我们将涵盖如何拉取外部数据到你的Ionic应用。在进入具体学习从外部服务获取数据之前,我得先涵盖一点我们将要做什么的知识。

映射与过滤数据

map映射filter过滤功能非常强大,允许你对数组做很多事情。这些不是神奇的ES6或者Angular 2功能,他们是JavaScript的一部分。
简单起见,映射针对数组的每个数据,对他运行一些函数,这些函数可能会改变数据的值,然后将他放到一个新的数组中。他将一个数组的每个值映射到一个新的数组。为了给你一个直观的理解,以下范例展示他为何有用,假定你有一个文件名的数字,这样:

  1. ['file1.jpg', 'file2.png', 'file3.png']

然后你就可以将这些值的完整路径映射到一个新的数组:

  1. ['http://www.example.com/file1.jpg', 'http://www.example.com/file2.png','http://www.example.com/file2.png']

代码是这样的:

  1. let oldArray = ['file1.jpg', 'file2.png', 'file3.png'];
  2. let newArray = oldArray.map((entry) => {
  3. return 'http://www.example.com/' + entry;
  4. });

我们给map提供了一个函数用于返回修改的值。我们提供的函数会接受每一个值作为传入参数。

过滤映射很像,但是和映射到一个新的数组不同,他只会添加匹配指定标准的值到新的数组。我们就用之前的例如,但是这次我们想要返回所有包含.png文件的数组。代码如下:

  1. let oldArray = ['file1.jpg', 'file2.png', 'file3.png'];
  2. let newArray = oldArray.filter((entry) => {
  3. return entry.indexOf('.png') > -1;
  4. });

如果我们同时还要完整路径的话,我们可以将filtermap链接起来用:

  1. let oldArray = ['file1.jpg', 'file2.png', 'file3.png'];
  2. let newArray = oldArray.filter((entry) => {
  3. return entry.indexOf('.png') > -1;
  4. }).map((entry) => {
  5. return 'http://www.example.com/' + entry;
  6. });

这样,我们就先过滤出我们不需要的结果,然后给他们映射完整路径到一个新的数组。结果将是一个包含了完整路径的两个.png文件的数组。这个就先停止这里,当我们学习获取数据的范例的时候,为什么值得花费这么多精力去解释他将会很明了。

Observables 和 Promises

如果你用过Ionic 1或者有很强劲的JavaScript背景的话,那么你可能熟悉Promises,但是熟悉Observables的人非常少。Observable核心新功能之一,包括Angular2(RxJS提供),所以理解他是什么他们和Promise的区别非常重要(他们看起来和表现起来都很像)。
在进入Observable之前,我们先具体了解一下Promise是什么。当我们编写异步(意思是代码不是顺序执行的)代码的时候,Promise就介入了。在使用HTTP请求数据的时候,我们需要等待数据返回,由于这个返回可能需要等待1-10秒钟,我们不可能在这个等待阶段同步停止整个应用。我们需要保持应用中等待的同时一直运行接受新的用户输入,当最终得到请求返回的时候,我们再对他进行处理。
Promise就是用来对付这种情况的,如果你熟悉回调函数,那么它就是相同的理念,只是更好一些。假设我们有一个方法名为getFromSlowServer() 返回一个promise,我们这样去使用:

  1. getFromSlowServer().then((data) => {
  2. console.log(data);
  3. });

我们调用Promise提供的then方法,这个方法是意思就是“已收到服务端返回数据,用他来处理”。在这个例子中,我们将返回数据传入一个将他打印到控制台的方法。这样,我们的应用就可以去做别的事情了,当数据返回时他就会执行以上代码。你可以想想爱你个你在工作并在写报告,你需要一些其他信息,所以你让你的助手去帮你找,但是你不需要坐在那里等你的助手回来 — 你继续写你的报告然后在你的助手回来的时候你就用上那些信息。
我们现在知道Promise是什么了,然后Observable是什么他能做什么Promise做不到的?
Obeservable和Promise服务的是同一个目标,但是他做了一些额外的事情。最主要的不同是Promise返回单纯的数据,但是一个Observable是一个流(stream)可以多次收到数据。将Observable看作流更简单,因为他们本身就是,他们叫做Observable是因为他们是可观测的(因为我们可以检测从流收到的数据)。
Observable看起来和Promise很像,但是他是用subscrible而不是then。由于Promise只是返回纯粹的值,所以在数据返回的时候可以直接使用。我也说过,Observable是一个流可以收到很多值,所以订阅它( 和你喜欢的Youtube频道 一样)也很合理,在每次收到数据时运行一些代码。看起来大概是这样的:

  1. someObservable.subscribe((result) => {
  2. console.log(result);
  3. });

很明显在进行HTTP请求的时候我们的应用需要等待数据返回,因此Promise和Observable非常有用。这不是唯一需要异步编程的例子。有一些不那么明显的场景例如从获取本地存储数据,甚至从用户相机获取相片,这些场景都需要等待处理结束才能使用具体数据。
如果想深入以上讨论的东西,强烈推荐交互手册。他介绍了RxJS,里面包括了Observable,也建立了如何使用mapfilter和其他功能的坚实基础。如果想更具体的了解Observable与Promise的不同,强烈推荐egghead.io视频

利用Http从服务端拉取数据

好了,你可能已经有了理论知识的武装 —— 我们来学习一个例子。这里我要用Reddit API来举例,因为他是一个公开访问且非常容易使用的接口。如果你订购了本书的包包括Giflists应用,那么我们后面会更详细地了解这个。
你可以简单的通过访问以下格式的URL来从subreddits创建一个帖子的JSON反馈(feed):
https://www.reddit.com/r/gifs/top/.json?limit=10&sort=hot

如果你点击以上链接,你会看到一个从gifssubreddit返回的返回的JSON反馈,包括10个子任务,以热度排序。如果你不熟悉JSON的话,我建议你阅读一下 这个 — 但是实际上它叫做JavaScript Object Notation是一个很好的传输数据的方式因为他可读性很好,电脑解析也很容易。如果你之前这样创建过JavaScript对象:

  1. var myObject = {
  2. name: 'bob',
  3. age: '43',
  4. hair: 'purple'
  5. };

那么你稍微整理一下就可以很轻松的阅读JSON反馈。但是我们要怎样将他带入到我们的Ionic 2应用中呢?
答案是使用Angular 2提供的Http服务,他允许你发起HTTP请求。如果你不知道HTTP请求是什么,基本上浏览器每次加载任何东西(文档,图片,文件等等)的时候,他就会发起HTTP请求。所以我们可以发起一个页面的HTTP请求,然后可以返回一些JSON数据给我们的应用使用。
首先我们需要设置Http服务,我们来看一个测试页面导入并注入了这个服务:

  1. import { Component } from '@angular/core';
  2. import { Http } from '@angular/http';
  3. import 'rxjs/add/operator/map';
  4. @Component({
  5. selector: 'page-one'
  6. template: 'page-one.html'
  7. })
  8. export class Page1 {
  9. constructor(public http: Http) {
  10. }
  11. }

由于咱们注入了Http服务,通过public修饰符我们可以使用this.http来自类里面使用。同时注意,我们从RxJS库里面导入了map操作符。之前讲过map是数组默认提供的函数 — 所以为什么我们需要从一个奇怪的库里面导入呢?因为Http服务并不会返回一个数组,他返回的是Observable。RxJS库为Observable提供了map函数,所以我们的先进行导入。
现在我们看一下向reddit URL发起请求的代码:

  1. import { Component } from '@angular/core';
  2. import { Http } from '@angular/http';
  3. import 'rxjs/add/operator/map';
  4. @Component({
  5. selector: 'page-one'
  6. template: 'page-one.html'
  7. })
  8. export class Page1 {
  9. constructor(public http: Http) {
  10. this.http.get('https://www.reddit.com/r/gifs/new/.json?limit=10').map(res => res.json()).subscribe(data => {
  11. console.log(data);
  12. });
  13. }
  14. }

调用的第一部分给咱们返回一个Observable。然后我们使用map函数,在其中将JSON文本通过json()函数将他对象化。这样我们就能更好的去用了。
重要:记住,Http请求是异步的。意思是你的代码只会在拉取到数据之后才会向前执行,这里过程可能是几毫秒到10秒,甚至不会返回。所以应用得预先做好应对。如果你运行如下代码:

  1. this.posts = null;
  2. this.http.get('https://www.reddit.com/r/gifs/top/.json?limit=2&sort=hot').map(res => res.json()).subscribe(data => {
  3. this.posts = data.data.children;
  4. });
  5. console.log(this.posts);
  6. You would see null output to the console. But if you were to run:
  7. this.posts = null;
  8. this.http.get('https://www.reddit.com/r/gifs/top/.json?limit=2&sort=hot').map(res => res.json()).subscribe(data => {
  9. this.posts = data.data.children;
  10. console.log(this.posts);
  11. });

你可以看到帖子输出到控制台,因为subscribe函数里面的所有事物都会在数据返回的时候运行。
返回我们的例子,在我们映射完响应之后我们链了一个subscribe调用在其中我们可以使用通过流(Observable)提交来的数据。之前说过Observable有用因为我们可以一直监听不同的值…但是为什么在这里用他?Http调用只会返回一次结果,为什么不用Promise替代Observable来避免混淆大家视听?
此处偏爱Observable而不是Promise的原因是Observable可以做Promise做的所有,在场景外技术上更好,可以做Promise做不到的其他事情。例如我们可以设置一个定时器interval来每5秒或者10秒发起Http请求,我们可以简单的设置debouncing保证请求不会太频繁的发起(预防向服务器大量发起请求),如果在旧请求结果返回之前Observable可以取消‘在发’请求等等。
以下是Giflists应用中的代码:

  1. this.subredditControl.valueChanges.debounceTime(1000)
  2. .distinctUntilChanged().subscribe(subreddit => {
  3. this.subreddit = subreddit;
  4. if(this.subreddit != ''){
  5. this.changeSubreddit();
  6. }
  7. });

在这里subredditControl是一个Observable。用户使用应用里输入就可以控制他的值。subscribe里面的代码只会在1秒(debounceTime)内不在变动的情况和新值和之前的值不一样的情况下执行。这个例子可以很清晰的表达subscribe帮你处理了多少奇怪的东西。
如果看到上面的例子你想‘哇… Ionic 2太难了,我要回去找Ionic 1!’,最好不要!这是举证Observable的一个高级范例,Ionic 2输入处理和双向数据绑定和Ionic 1一样简单。
Observable是一个大的主题,还有成吨的东西需要去学习。如果你不知道啥情况的话也不要被吓到。在制作Ionic应用的时候最好对Obserable有一个比较好的认识,但是对于Ionic来讲,他只是理解就好(虽然他比较重要但是你不是经常用到)你同样可以过的很好。

从自己的服务器拉取数据

我们知道了如何使用JSON反馈从类似reddit提供的服务器拉取数据,但是如果你想拉取自己的数据呢?你要怎样才能设置你自己的JSON反馈呢?
详细深入如何设置你自己的API超出了本课程的范围,但是我会给你指出大概。基本上:

  • 1.在Ionic应用里面发起请求到你的服务器URL
  • 2.从你的服务器拉取数据,服务器语言随你喜欢
  • 3.以JSON格式将需求的数据打印到页面

我会带你通过PHP实现一个简单的API,— 你可以用你自己喜欢的语言 —— 这样你就可以输出JSON到浏览器:

  • 1.创建一个文件名为feed.php,文件可以通过 http://www.mywebsite.com/api/feed.php 访问到
  • 2.获取数据。本案例中我通过查询MySQL,当然数据来源可以是不同的方式:
    ```php
    $mysqli = new mysqli(“localhost”, “username”, “password”, “database”);
    $query = “SELECT * FROM table”;
    $dbresult = $mysqli->query($query);

while($row = $dbresult->fetch_array(MYSQLI_ASSOC)){
$data[] = array(
‘id’ => $row[‘id’],
‘name’ => $row[‘name’]
);
}

if($dbresult){
$result = “{‘success’:true, ‘data’:” . json_encode($data) . “}”;
}
else {
$result = “{‘success’:false}”;
}

  1. - 3.将数据JSON化输出到浏览器:```echo($result);

之前讲过,可以使用任何服务端语言,可以使用任何存储机制。只要能够以JSON格式获得你要的数据,然后输出到浏览器就可以了。

这个给了你一个很合理的概观:如何使用Ionic 2 和Http服务来获取远程数据。这里的代码第一眼看来语法和概念可能有些花俏,但是当你进入基础的时候会发现你需要知道的不是太多。你的数据可能更复杂,你也许想要对他进行更神奇的操作,或者以不同的方式在显示,但是基本原理还是一致的。

译者:这两章翻译得有点烂,因为心情焦躁