在上一篇介紹 PubSub 的方法後,發現該方法不適合處理一次性事件
,而Promise
就是用來解決該問題的手法。那什麼是Promise
呢?,他是一種非同步操作的最終結果
,你也可以把想成是未來的物件但是現在還不可用,在未來他會有多種狀況,可能是成功
又或是失敗
,當未來發生成功
時他就執行成功的 callBack fucntion,但它
失敗`時就執行失敗的callback function。
Promise/A+ 規範
上述說的promise
只能說是一種概念,然後有很多人會針對它進行實作,但是因為都沒個規範,所以每個人做出來的promise
都不太一樣,因此Kris Zyp
提出了 CommonJs 的 promises/A 規範,符合條件如下。
規範 1 : Promise狀態
一個Promise
必須要處於以下三種狀態。pending, fulfilled, or rejected
- pending : 當為Pending 狀態時,可以轉換至f fulfilled 或 rejected。
- fulfilled : 通常是代表成功。
- rejected : 通常代表失敗。
規範 2 : Promise必須要 Then
一個Promise必須提供Then方法,並且接受兩個參數,並且第一個參數onFulfilled
為fulfilled
執行後調用,而onRejected
為rejected
後調用。
promise.then(onFulfilled, onRejected)
其它詳細的規範其參考下面的連結。。
Jquery 的 Promise 實現
Jquery
在1.5
之後,我們常用的$.ajax
、$.get
、$.getJson
等這些ajax
函數全部都會返回promise
,下面給個例子來看看差別。
versin 1.4
$.get('/getData', {
success: onSuccess,
failure: onFailure
});
version 1.5
var promise = $.get('/getData');
promise.done(onSuccess);
promise.fail(onFailure);
這種改變的好處在於封裝
,你可以將複雜非同步處理輕鬆的模式化,例如希望任務1與任務2完成時在執行任務3,或是任務1執行完在執行任務2這種複雜的非同步任務都可以用promise
來解決。
Jquery的 Promise 與 Deferred
我們要如何把一個函數變成Promise
對象?首先我們需要產生Deferred
,什麼是Deferred
?Deferred
嚴格來說為Promise
的超集合,它比Promise
多了一項關鍵特性,那就是他可以直接的觸發,單純的Promise
實例只能允許增加callBack function
,必須由其它東西來觸發這些callback function
。
如下程式碼,如果我們要將deferredTest
變成Promise
,我們需要一個deferred
,然後在執行成功時,執行deferred.resolve()
,並且失敗時執行deferred.reject()
,然後它們就會觸發相對應的callback function
。
function deferredTest(){
var deferred = new $.Deferred();
$.ajax('.getData',{
success:function(){
deferred.resolve();
},
fail:function(){
deferred.reject();
}
});
return deferred;
}
根據範例deferred.resolve()
就會觸發onSuccess
,而deferred.reject()
則會觸發onFailure
。
var promise = deferredTest();
promise.done(onSuccess);
promise.fail(onFailure);
向 callback function 傳遞參數
通常在實務上,ajax
很常去後端取得資料,因此通常都有回傳資料,這時可以提供資料給callback function
,如下程式碼。
function getData(){
var deferred = new $.Deferred();
$.ajax('.getData',{
success:function(data){
deferred.resolve(data);
},
fail:function(errorMessage){
deferred.reject(errorMessage);
}
});
return deferred;
}
下面程式碼的data
就是ajax
回傳的資料。
var promise = getData();
promise.done(function onSuccess(data){
console.log(data);
});
ES6 的 Promise
在 es6 時它有提供內建的 promise ,它有提供以下幾種方法 :
Promise.all
回傳一個 promise
這種用方法,可以指定一系列的 promise 任務,不依照順序來執行,當其中一個任務失敗時,它就會 rejects ,而當所以任務都完成後,它就會 resolve。
下面程式碼中,為我們使用promise.all
的結果,我們傳入的任務順序是3 (3秒) -> 2 (2秒) -> 1(1秒)
。
function handler(url, time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(url);
resolve(url);
} , time)
});
};
function process(objs){
let tasks = [];
objs.forEach((obj) => {
tasks.push(handler(obj,obj*1000));
});
return Promise.all(tasks);
}
var test = [3, 2, 1];
process(test).then(() => {
console.log("All success")
});
然後我們執行的結果如預期,不會依順序來執行,輸入的結果是依時間花時的順序。
1
2
3
All success.
Promise.race
回傳一個 promise
它是傳入一系列的 promise 任務,只要其中一個任務失敗或成都,就會直接 resolve 或 reject。但其它的任務還是會繼續執行。
function handler(url, time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(url);
resolve(url);
} , time)
});
};
function process(objs){
let tasks = [];
objs.forEach((obj) => {
tasks.push(handler(obj,obj*1000));
});
return Promise.race(tasks);
}
var test = [3, 2, 1];
process(test).then(() => {
console.log("All success")
});
執行結果。
1
All success
2
3
Promise.resolve 與 Promise.reject
回傳一個 promise
這兩個方法都會回傳一個 promise ,差別在於,一個已經resolve
,另一個已reject
,簡單的使用範例如下。
Promise.resolve(2).then((data) => {
console.log(data);
})
// console.log 結果為 2
而 reject 的用法如下 :
Promise.reject("error message").catch((err) => {
console.log(err);
})
// console.log 結果為 "error message"
我們如果要依序執行要如何做 ?
我們這邊直接寫個最簡單的方法,這邊我們需要一個 promise.resolve 來產生一個已經 resolve 後的 promise ,然後在每一次的 result.then 裡面回傳 handler 回傳結的 promise ,這樣就能產生像 promise chain 的東西。
function handler(url, time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(url);
resolve(url);
} , time)
});
};
function process(objs) {
let result = Promise.resolve();
objs.forEach((obj) => {
result = result.then(() =>{
return handler(obj,obj*1000);
});
})
return result;
}
var test = [3, 2, 1];
process(test).then(() => {
console.log("All success")
});
執行結果
3
2
1
All success
不過這種寫法有個缺點,那就是如果有很多任務時,他會需要先全部註冊然後再來執行,建議使用像是buldbird 這種 promise libary 來處理會比較優質點兒 ~