promise vs Observable(js小笔记)

1.promise

在传统的解决方案中,js实现异步编程采用的方法是回调函数和事件监听(事件发布订阅),可是当应用很复杂很庞大时,大量的回调会让调试程序变得举步维艰,成为开发者的噩梦。php

promise是在es6标准中的一种用于解决异步编程的解决方案,因为在语言级别上,不一样于Java、Python等多线程语言,js是单线程的,因此在node.js中大量使用了异步编程的技术,这样作是为了不同步阻塞。
node

promise意为承诺,拟定一个承诺,当承诺实现时即返回结果,不受其余操做的影响,能够把它理解为一个简单的容器,里面存放着一个未来会结束的事件返回结果(即异步操做)。不一样于传统的回调函数,在promise中,全部的异步操做的结果均可以经过统一的方法处理。promise有三种状态:es6

pending(进行中),resolved(成功),rejected(失败),异步操做的结果决定了当前为哪种状态,promise的状态只有两种改变状况,且仅改变一次:由pending转变为resolved,由pending转变为rejected,结果将会保持不变。web

下面代码是一个简单的promise实例:编程

const promise = new Promise(function(resolve, reject){
//some code
if(/*异步操做成功*/){
    resolve(value);
} else {
    reject(error);
}
});复制代码

Promise构造函数接受一个函数做为参数,该函数的两个参数分别是resolvereject。它们是两个函数。promise实例生成后,能够用then方法分别指定resolvedrejected的回调函数。其中第二个函数是可选的,下面是一个Promise对象的简单例子:
json

function timeout(ms){
    return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, "done");
    });
}

timeout(100).then((value) => {
    console.log(value);
});复制代码

timeout方法返回一个Promise实例,表示一段时间之后才会发生的结果,过了指定的时间后,Promise状态变为resolved,触发then方法绑定的回调函数。api

let promise = new Promise(function(resolve, reject){
    console.log("Promise");
    resolve();
});

promise.then(function(){
    console.log("resolved.");
});
console.log("Hi!");
// Promise
// Hi!
// resolved.复制代码

上面代码中,Promise新建后当即执行,因此首先返回的是Promise,而后,then方法指定的回调函数,将在当前脚本全部同步任务执行完才会执行,因此resolved最后输出。数组

以上简单介绍了promise的基本特性:一旦建立当即执行;三种状态:执行中,成功,失败;结果不受其余操做影响,结果不可取消;当异步操做完成或失败时,Promise会处理一个单个事件。promise

2.Observable

Observable便可观察对象,在不少软件编程任务中,或多或少你都会指望你写的代码能按照编写的顺序,一次一个的顺序执行和完成。可是在ReactiveX(基于一系列可观察的异步和基础事件编程组成的一个库)中,不少指令多是并行执行的,以后他们的执行结果才会被观察者捕获,顺序是不肯定的。为达到这个目的,你定义一种获取和变换数据的机制,而不是调用一个方法。在这种机制下,存在一个可观察对象(Observable),观察者(Observer)订阅(Subscribe)它,当数据就绪时,以前定义的机制就会分发数据给一直处于等待状态的观察者哨兵。bash

ReactiveX 可见模式容许你使用数组等数据项的集合来进行些异步事件流组合操做。它使你从繁琐的web式回调中解脱,从而能使得代码可读性大大提升,同时减小bug的产生。

  • observable数据是很灵活的,不一样于promise只能处理单个值,observalbe支持多值甚至是数据流。
  • 当observalbes被建立时,它是不会当即执行的(lazy evaluation),只有当真正须要结果的时候才会去调用它。例以下面的代码,对于promise而言,不管是否调用then,promise都会被执行;而observables却只是被建立,并不会执行,而只有在真正须要结果的时候,如这里的foreach,才会被执行。

var promise = new Promise((resolve) => {      setTimeout(() => {          resolve(42);      }, 500);      console.log("promise started");  });    //promise.then(x => console.log(x));    var source = Rx.Observable.create((observe) => {      setTimeout(() => {          observe.onNext(42);      }, 500);      console.log("observable started");  });    //source.forEach(x => console.log(x));  复制代码

  • observables是能够取消的(dispose),observables可以在执行前或执行中被取消,即取消订阅。下面的例子中,observable在0.5秒的时候被dispose,因此日志“observable timeout hit”不会被打印。

var promise = new Promise((resolve) => {      setTimeout(() => {          console.log("promise timeout hit")          resolve(42);      }, 1000);      console.log("promise started");  });    promise.then(x => console.log(x));    var source = Rx.Observable.create((observe) => {      id = setTimeout(() => {          console.log("observable timeout hit")          observe.onNext(42);      }, 1000);      console.log("observable started");        return () => {          console.log("dispose called");          clearTimeout(id);      }  });    var disposable = source.forEach(x => console.log(x));    setTimeout(() => {      disposable.dispose();  }, 500);  复制代码

  • observables能够屡次调用(retry),对于一个observable对象返回的结果,能够被屡次调用处理,可以触发屡次异步操做,在observables中封装了不少工具方法能够用来操做observable结果,对其进行组合变换。在上面的代码中,能够拿到promise和observable的变量。对于promise,不论在后面怎么调用then,实际上的异步操做只会被执行一次,屡次调用没有效果;可是对于observable,屡次调用forEach或者使用retry方法,可以触发屡次异步操做。

下面再经过一个angular2实例场景了解promise和observable在实际应用中的区别:

首先,咱们来定义一下问题的场景。假设咱们要实现一个搜索功能,有一个简单的输入框,当用户输入文字的时候,实时的利用输入的文字进行查询,并显示查询的结果。

在这个简单的场景当中,通常须要考虑3个问题:

  • 不能在用户输入每一个字符的时候就触发搜索。
    若是用户输入每一个字符就触发搜索,一来浪费服务器资源,二来客户端频繁触发搜索,以及更新搜索结果,也会影响客户端的响应。通常这个问题,都是经过加一些延时来避免。
  • 若是用户输入的文本没有变化,就不该该从新搜索。
    假设用户输入了’foo’之后,停顿了一会,触发了搜索,再敲了一个字符’o’,结果发现打错了,又删掉了这个字符。若是这个时候用户又停顿一会,致使触发了搜索,此次的文本’foo’跟以前搜索的时候的文本是同样的,因此不该该再次搜索。
  • 要考虑服务器的异步返回的问题。
    当咱们使用异步的方式往服务器端发送多个请求的时候,咱们须要注意接受返回的顺序是没法保证的。好比咱们前后搜索了2个单词’computer’, ‘car’, 虽然’car’这个词是后来搜的,可是有可能服务器处理这个搜索比较快,就先返回结果。这样页面就会先显示’car’的搜索结果,而后等收到’computer’的搜索结果的时候,再显示’computer’的结果。可是,这时候在用户看来明明搜索的是’car’,却显示的是另外的结果。
首先是promise的初始版本:

import { Injectable } from '@angular/core';
import { URLSearchParams, Jsonp } from '@angular/http';
@Injectable()
export class WikipediaService {  

constructor(private jsonp: Jsonp) {}  

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 })       
    .toPromise()             
    .then((response) => response.json()[1]);  
            }
        }复制代码

在上面代码中,使用Jsonp模块来请求api结果,它的结果应该是一个类型为Observable<Response>的对象,咱们把返回的结果从Observable<Response> 转换成 Promise<Response>对象,而后使用它的then方法把结果转成json。这样,这个search方法的返回类型为Promise<Array<string>>。这样虽然查询功能基本实现了,可是前面提到的三个问题都没有解决。

下面应用observable实现功能:

(1)控制用户输入延时

export class AppComponent { 
      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));  
        }
    }复制代码

这里的this.term.valueChanges是一个Observable<string>对象,经过debounceTime(400)咱们设置它的事件触发延时是400毫秒。这个方法仍是返回一个Observable<string>对象。而后咱们就给这个对象添加一个订阅事件

subscribe(term => this.wikipediaService.search(term).then(items => this.items = items));复制代码

这样就解决了第一个问题,经过控制用户输入延时来解决每次输入一个字符就触发一次搜索的问题。

(2)防止触发两次

this.term.valueChanges           .debounceTime(400)           .distinctUntilChanged()           .subscribe(term => this.wikipediaService.search(term).then(items => this.items = items));复制代码

上面的代码解决了第二个问题,就是通过400ms的延时之后,用户输入的搜索条件同样的状况。Observable有一个distinctUntilChanged的方法,他会判断从消息源过来的新数据跟上次的数据是否一致,只有不一致才会触发订阅的方法。

(3)处理返回顺序

上面描述了服务器端异步返回数据的时候,返回顺序不一致出现的问题。对于这个问题,咱们的解决办法就比较直接,也就是对于以前的请求返回的结果,直接忽略,只处理在页面上用户最后一次发起的请求的结果。咱们能够利用Observabledispose()方法来解决。实际上,咱们是利用这种’disposable’特性来解决,而不是直接调用dispose()方法。(实在不知道该怎么翻译’disposable’,它的意思是我能够停止在Observable对象上的消息处理,字面的意思是可被丢弃的、一次性的。)

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]);
}复制代码

注意这个方法最后用.map((response) => response.json()[1]),意思是对于原先的Response类型的结果,转换成实际的搜索结果的列表,即利用Observable的特性去丢弃上一个未及时返回的结果。

总结:在处理某些复杂异步应用中,observable比promise更受开发者青睐,由于使用Observable建立的异步任务,能够被处理,并且是延时加载的。而promise设计的初衷只是为了解决大量的异步回调所形成的难以调试问题,observable封装了大量的方法供咱们使用以处理复杂的异步任务。

以上内容部分摘自阮一峰阮老师的《es6入门》,关于promise和observable的区别,还有一个较好的视频:egghead.io上的这个7分钟的视频(做者Ben Lesh)。第一次在掘金上写文章,算是对我的知识的总结,不免会有错漏,欢迎你们指点。

相关文章
相关标签/搜索