[Ionic 2从入门到精通] 2.11 获取数据,Observable和Promise

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

映射与过滤数据

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

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

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

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

代码是这样的:

let oldArray = ['file1.jpg', 'file2.png', 'file3.png'];

let newArray = oldArray.map((entry) => {
    return 'http://www.example.com/' + entry;
});

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

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

let oldArray = ['file1.jpg', 'file2.png', 'file3.png'];

let newArray = oldArray.filter((entry) => {
    return entry.indexOf('.png') > -1;
});

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

let oldArray = ['file1.jpg', 'file2.png', 'file3.png'];

let newArray = oldArray.filter((entry) => {
    return entry.indexOf('.png') > -1;
}).map((entry) => {
    return 'http://www.example.com/' + entry;
});

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

Observables 和 Promises

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

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

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

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

很明显在进行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对象:

var myObject = {
    name: 'bob',
    age: '43',
    hair: 'purple'
};

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

import { Component } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';

@Component({
    selector: 'page-one'
    template: 'page-one.html'
})

export class Page1 {
    constructor(public http: Http) {
        
    }
}

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

import { Component } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';

@Component({
selector: 'page-one'
template: 'page-one.html'
})

export class Page1 {
    constructor(public http: Http) {
        this.http.get('https://www.reddit.com/r/gifs/new/.json?limit=10').map(res => res.json()).subscribe(data => {
            console.log(data);
        });
    }
}

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

this.posts = null;
this.http.get('https://www.reddit.com/r/gifs/top/.json?limit=2&sort=hot').map(res => res.json()).subscribe(data => {
    this.posts = data.data.children;
});

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

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

this.subredditControl.valueChanges.debounceTime(1000)
  .distinctUntilChanged().subscribe(subreddit => {
    this.subreddit = subreddit;

    if(this.subreddit != ''){
        this.changeSubreddit();
    }
});

在这里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,当然数据来源可以是不同的方式:
$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}";
}

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

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容