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)。第一次在掘金上寫文章,算是對我的知識的總結,不免會有錯漏,歡迎你們指點。

相關文章
相關標籤/搜索