es6 - Promise

es6 promise與異步編程

對於一些還不具有大量編程經驗的朋友來講,promise多是es6比較難以掌握的點。首先是不少名詞,好比Promises,es6 Promise, 回調函數(callback),Promise/A+,異步編程等。下面就首先介紹下這些名詞的含義和區別。es6

所謂異步編程中的異步是相對於同步的概念的。js是單線程的語言,同一時間只能作一件事,爲了指定一些稍後要執行的代碼,咱們須要異步。在客戶端,主要的異步方式有事件,setTimeout,Ajax等。Node的發展大大擴展了js語言的邊界,咱們知道,Node使用非阻塞IO模型,它使用回調函數模式來實現異步編程。好比:編程

readFile('example.txt', function(err, contents){
    if(err){ throw err; }
    console.log(contents);
});
console.log('Hi!');

上面代碼中readFile的第二個參數就是回調函數。它會在讀取完example.txt後被添加到執行隊列中。上面代碼的執行順序是--執行readFile函數,在遇到讀取文件時暫停,打印"Hi",讀取文件結束後將回調添加到做業隊列中,執行回調函數,打印contents。segmentfault

原本呢,使用回調函數是可以完成異步編程的。可是隨着代碼的邏輯越複雜,這種異步編程方式愈來愈難以閱讀和追蹤程序錯誤,因此發展出了Promises規範來完成異步編程。數組

Promises是一系列異步編程規範的統稱。咱們須要瞭解的是其中的Promise/A+規範。es6經過Promise這個內建對象實現了該規範。因此咱們可使用es6中的Promise對象來進行異步編程。promise

下面將對es6中的Promise對象進行介紹。至於jQuery中延遲對象$.deferred(),根據規範本身實現promise和ES7的Async/Await異步方式等更多內容,後面會專門寫一篇文章進行介紹。異步

語法

Promise的3種狀態

一個promise實例有3種狀態,分別是:異步編程

  • pending -- 掛起,表示Promise結果還未知。
  • fulfilled -- 已完成, 表示Promise成功完成。
  • rejected -- 已拒絕,表示Promise未成功結束。

promise處於這3種狀態中的一種,而且能夠由pending狀態變爲fulfilled狀態,或由pending變爲rejected狀態。反之則不行。函數

爲了便於理解,下面將經過一個生活化的例子,來解釋什麼是Promise?學習

Promise是允諾的意思。它就是一個關於未發生的事情的承諾。好比:this

你訂了一份燒烤,店家說半個小時內送到,這就是一個Promise。如今,這個Promise尚未發生,因此可能半個小時內配送成功或者失敗。對此,你預備了兩種處理方式:成功 -- 美滋滋的吃燒烤,失敗 -- 去樓下店裏吃。

在半個小時內,這個Promise處於pending狀態,你正常上網,擼代碼。一段時間後,這個promise就有告終果。是成功(fulfilled)或者失敗(rejected)。根據這個結果,你以前的兩種處理方式就會相應執行。這就是promise。

對應的代碼以下:

let promise = new Promise(function(resolve, reject){
    //等待店家送來中...
    let result = '配送成功'? true : false;
    if(result){
        resolve(value);
    }else{
        reject(reason);
    }
});

promise.then(function(value){
    //美滋滋吃燒烤...
    //value爲上面resolve()中傳遞的值, 好比共100塊錢。
}, function(reason){
    //叫上隔壁老王去樓下吃...
    //reason爲上面reject()的傳遞的緣由,好比烤糊了...
});

上面代碼就是經過promise異步編程的代碼。這裏要注意的是Promise構造函數接收一個函數做爲參數,函數內部是異步的邏輯。這個函數接收兩個參數:resolve和reject。resolve()能夠把promise推向fulfilled狀態,reject()能夠把promise推向rejected狀態。

promise有個then方法,用於處理promise成功或失敗後的邏輯。then有兩個參數:
參數1爲promise成功時執行的函數,該函數的參數value對應於上面resolve(value)中的value值;
參數2爲promise失敗時執行的函數,該函數的參數reason對應於reject(reason)中的reason值,表示失敗的緣由。
一旦promise有告終果(成功或失敗),就會執行對應then中的函數。

經過Promise處理Ajax的例子

Ajax是客戶端最經常使用的異步編程場景,下面一個例子演示了使用Promise進行Ajax操做的代碼。

function getData(method, url){
  let promise = new Promise(function(resolve, reject){
    let xmlHttp = new XMLHttpRequest();
    xmlHttp.open(method, url);
    xmlHttp.send();
    xmlHttp.onload = function () {
      if (this.status == 200 ) {
        resolve(this.response);
      } else {
        reject(this.statusText);
      }
    };
    xmlHttp.onerror = function () {
      reject(this.statusText);
    };
  })
  return promise;
}

getData('get','www.xxx.com').then(successFun, failFun);

function successFun(value){
  //Ajax成功處理函數...
}
function failFun(reason){
  //Ajax失敗處理函數...
}

建立一個已決的Promise

前面的例子promise建立時,promise都處於pending狀態,根據異步操做的結果將promise推向成功或失敗狀態。

Promise類型有兩個靜態方法Promise.resolve(value),Promise.reject(reason)能夠分別建立已是fulfilled和已是rejected狀態的promise。
好比:

let promise = Promise.resolve(44);
promise.then(function(value){
  console.log('fulfilled', value);
})

上面代碼promise在被建立出來時,已是fulfilled狀態,接下來會直接將then中的回調函數加入到做業隊列中,等待做業隊列中前面的任務完成後執行該函數。

這裏傳入Promise.resolve(value)和Promise.reject(reason)中的參數和以前Promise構造是對應的參數是同樣的。

Promise.prototype.then()和Promise.prototype.catch()

上面已經演示過promise實例上then方法的用法,每個promise實例還具備catch方法。

catch()方法只處理reject的狀況,他的行爲與調用Promise.prototype.then(undefined, onRejected)相同。好比:

let p = new Promise(function(resolve, reject){
    //...
    reject(new Error('something wrong!'))
})
p.catch(function(reason){
    //拒絕
})

上面catch方法中的回調在promise被reject時調用。

then()和catch()的返回值

每次對then()或catch()的調用都會返回另外一個promise,這也是不少代碼能夠寫成相似鏈式調用的緣由。好比:

let p1 = new Promise(function(resolve, reject){
  resolve(42);
});
let p2 = p1.then(function(value){
  console.log(value);
})

p2.then(function(){
  console.log("Finished");
}, function(){
    console.log('something wrong!');
});

p1 == p2 // false,注意:p1.then()會返回一個新的promise,因此p1與p2並不相等

//能夠寫成鏈式調用的形式,好比
p1.then(function(value){
  console.log(value)
}).then(function(){
  console.log('do something');
}).then(function(){
  console.log('Finished');
})

在上面代碼中,p1.then()返回了一個promise爲p2, 那麼p2的狀態和p1之間有什麼關係呢?

更具體一點說,當p1變爲fulfilled時,p1.then()返回的p2是什麼狀態呢?兩者有什麼聯繫呢?

p2的行爲與p1.then()中回調函數的返回值有關:

  • 若是then中的回調函數拋出一個錯誤,或者回調函數中調用reject(reason),那麼then返回的Promise將會成爲拒絕狀態,而且將拋出的錯誤做爲拒絕狀態的回調函數的參數值。
  • 若是then中的回調函數返回一個值,那麼then返回的Promise將會成爲接受狀態,而且將返回的值做爲接受狀態的回調函數的參數值。
  • 若是then中的回調函數返回一個已是接受狀態的Promise,那麼then返回的Promise也會成爲接受狀態,而且將那個Promise的接受狀態的回調函數的參數值做爲該被返回的Promise的接受狀態回調函數的參數值。
  • 若是then中的回調函數返回一個已是拒絕狀態的Promise,那麼then返回的Promise也會成爲拒絕狀態,而且將那個Promise的拒絕狀態的回調函數的參數值做爲該被返回的Promise的拒絕狀態回調函數的參數值。
  • 若是then中的回調函數返回一個未定狀態(pending)的Promise,那麼then返回Promise的狀態也是未定的,而且它的終態與那個Promise的終態相同;同時,它變爲終態時調用的回調函數參數與那個Promise變爲終態時的回調函數的參數是相同的。
  • 若是then中的回調函數無顯式的返回值,而且也沒有調用reject(),那麼返回的Promise爲接收狀態。

Promise.all()處理多個promise

Promise內建對象上的靜態方法Promise.all()用於處理多個promise的狀況。

Promise.all([promise1, promise2,...])返回一個promise的實例,接收一個promise組成的數組爲參數。只有當數組內的promise都成功時,纔會調用對應的then中的成功處理函數,只要有一個不成功,那麼調用對應的拒絕處理函數。

依然使用前面那麼訂燒烤的例子,你不只訂了燒烤,還在另外一家訂了啤酒。打算等到燒烤和啤酒都配送成功後一塊兒吃,美滋滋~~。好比:

Promise.all([訂燒烤,訂啤酒]).then(function(value){
    //吃燒烤,喝啤酒...
}, function(reason){
    //拒絕的緣由,烤糊了或者啤酒賣完了...
})

這裏要注意的一點是,對於數組中的promise,只要有任一個promise爲拒絕,那麼就會當即執行then中的拒絕處理函數,並不會等待其餘promise的結果。只有當全部promise的結果都成功時,才執行then中的成功處理函數。好比:

var p1 = new Promise(function(resolve, reject){
  setTimeout(function(){
    console.log('A');
    resolve();
  }, 1000)
});
var p2 = Promise.reject(new Error('error'));
var p3 = new Promise(function(resolve, reject){
  setTimeout(function(){
    console.log('B');
    resolve();
  }, 0)
});
Promise.all([p1,p2,p3]).then(function(value){
  console.log('success!');
}, function(reason){
  console.log('failed');
})
//結果爲failed B  A

因爲p2爲已拒絕狀態的promise,因此Promise.all()當即變爲拒絕狀態,打印failed,p1和p2會繼續執行,但對於Promise.all()的結果沒有影響。

Promise.race()處理多個promise

Promise內建對象上的靜態方法Promise.race()一樣用於處理多個promise的狀況。一樣返回一個Promise,一樣接收一個promise數組做爲參數。

與all不一樣的地方在於,數組中的promise就像在賽跑同樣(race),而且只關心第一名的狀況,只要有其中一個promise有告終果,Promise.race()的狀態就會當即與該promise相同。
數組中其餘promise繼續執行,但對於Promise.race()的結果沒有影響。

注意事項

  • 咱們構造promise實例的代碼是當即執行的,而then方法中的回調函數是異步調用的,在promise的狀態變爲成功或拒絕時,纔會把相應的處理函數添加到promise工做隊列中。而且該函數會先於setTimeout執行。例如:
var promise = new Promise(function(resolve, reject){
  console.log('A');
  resolve('C');
})

console.log('B');

setTimeout(function(){
  console.log('D');
},0)

promise.then(function(value){
  console.log(value)
});
//打印A, B, C, D
  • 若是then方法中傳入的參數被忽略,或者是非函數,好比:
p.then(function(value){
    //...
})
//或者
p.then(undefined, function(reason){
    //...
})

那麼,相應的回調處理函數被忽略,then方法返回的promise會保留上一個promise的狀態和參數。最典型的例子:

var p = new Promise(function(resolve, reject){
    reject(new Error('error'));
})
p.then(function(value){
    //...
}).then(function(value){
    //...
}).then(undefined, function(reason){
    console.log(reason);
})
//打印'error'

p的前兩次then調用的拒絕處理函數被忽略,而後reject狀態和錯誤信息就一直日後傳遞,直到被最後一次then調用捕獲。

最佳實踐

關於Promise的知識點不少,可是最經常使用的場景就是Ajax。好比:

function getData(method, url){
  var promise = new Promise(function(resolve, reject){
    //Ajax獲取數據的代碼...
    if(success){
      resolve(response)
    }else{
      reject(statusText)
    }
  })
  return promise;
}

getData('get','www.xxx.com').then(Fun1).then(Fun2).then(Fun3).catch(function(reason){
  //錯誤處理邏輯...
});

更多關於es6的內容,能夠關注右側個人專欄--學習ES6。

參考:
MDN Javascript Promise.
Promise介紹-基礎篇.《深刻理解ES6》-- Promise與異步編程。

相關文章
相關標籤/搜索