問題
JavaScript的Callback機制深入人心。而ECMAScript的世界同樣充斥的各種異步操作(異步IO、setTimeout等)。異步和Callback的搭載很容易就衍生"回調金字塔"。——由此產生Deferred/Promise。
Deferred起源于Python,后來被CommonJS挖掘并發揚光大,得到了大名鼎鼎的Promise,并且已經納入ECMAScript 6(JavaScript下一版本)。
Promise/Deferred是當今最著名的異步模型,不僅強壯了JavaScript Event Loop(事件輪詢)機制下異步代碼的模型,同時增強了異步代碼的可靠性。
有了 Promise ,就可以將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數。此外,Promise 對象提供統一的接口,使得控制異步操作更加容易。
概述
Promise 對象用于延遲(deferred) 計算和異步(asynchronous ) 計算。一個Promise對象代表著一個還未完成,但預期將來會完成的操作。
語法
new Promise(executor);
new Promise(function(resolve, reject) { ... });
例子:
<pre>
var promise = new Promise(function(resolve, reject) {
if (/** 異步操作成功* */){
resolve(value);
} else {
reject(error);
}
});
</br>
promise.then(function(value) {
// success
},
function(value) {
// failure
});
</pre>
參數
executor帶有 resolve、reject 兩個參數的函數對象。一旦我們的操作完成即可調用這些函數。
- 第一個參數用在處理執行成功的場景
- 第二個參數則用在處理執行失敗的場景。
描述
Promise對象是一個返回值的代理,這個返回值在promise對象創建時未必已知。它允許你為異步操作的成功或失敗指定處理方法。 這使得異步方法可以像同步方法那樣返回值:異步方法會返回一個包含了原返回值的 promise 對象來替代原返回值。
Promise對象有以下幾種狀態:
- pending: 初始狀態, 非 fulfilled 或 rejected.
- fulfilled: 成功的操作.
- rejected: 失敗的操作.
pending狀態的promise對象既可轉換為帶著一個成功值的fulfilled 狀態,也可變為帶著一個失敗信息的 rejected 狀態。
特點
對象的狀態不受外界影響,只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。
當狀態發生轉換時,promise.then綁定的方法(函數句柄)就會被調用。一旦狀態改變,就不會再變,任何時候都可以得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。
當綁定方法時,如果 promise對象已經處于 fulfilled 或 rejected 狀態,那么相應的方法將會被立刻調用, 所以在異步操作的完成情況和它的綁定方法之間不存在競爭條件。
因為Promise.prototype.then
和 Promise.prototype.catch
方法返回 promises對象, 所以它們可以被鏈式調用—— 一種被稱為 composition 的操作。
注意: 如果一個promise對象處在fulfilled或rejected狀態而不是pending狀態,那么它也可以被稱為settled狀態。你可能也會聽到一個術語resolved ,它表示promise對象處于settled狀態,或者promise對象被鎖定在了調用鏈中。關于promise的狀態, Domenic Denicola 的 States and fates 有更多詳情可供參考。
基本的api
- Promise.resolve()
- Promise.reject()
- Promise.prototype.then()
- Promise.prototype.catch()
- Promise.all() 完成全部
- Promise.race() 完成一個
常用的JavaScript的promise的寫法
<pre>
function get(uri){
return http(uri, 'GET', null);
}
</br>
function post(uri,data){
if(typeof data === 'object' && !(data instanceof String || (FormData && data instanceof FormData))) {
var params = [];
for(var p in data) {
if(data[p] instanceof Array) {
for(var i = 0; i < data[p].length; i++) {
params.push(encodeURIComponent(p) + '[]=' + encodeURIComponent(data[p][i]));
}
} else {
params.push(encodeURIComponent(p) + '=' + encodeURIComponent(data[p]));
}
}
data = params.join('&');
}
return http(uri, 'POST', data || null, {
"Content-type":"application/x-www-form-urlencoded"
});
}
</br>
function http(uri,method,data,headers){
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open(method,uri,true);
if(headers) {
for(var p in headers) {
xhr.setRequestHeader(p, headers[p]);
}
}
xhr.addEventListener('readystatechange',function(e){
if(xhr.readyState === 4) {
if(String(xhr.status).match(/^2\d\d$/)) {
resolve(xhr.responseText);
} else {
reject(xhr);
}
}
});
xhr.send(data);
})
}
</br>
function wait(duration){
return new Promise(function(resolve, reject) {
setTimeout(resolve,duration);
})
}
</br>
function waitFor(element,event,useCapture){
return new Promise(function(resolve, reject) {
element.addEventListener(event,function listener(event){
resolve(event)
this.removeEventListener(event, listener, useCapture);
},useCapture)
})
}
</br>
function loadImage(src) {
return new Promise(function(resolve, reject) {
var image = new Image;
image.addEventListener('load',function listener() {
resolve(image);
this.removeEventListener('load', listener, useCapture);
});
image.src = src;
image.addEventListener('error',reject);
})
}
</br>
function runScript(src) {
return new Promise(function(resolve, reject) {
var script = document.createElement('script');
script.src = src;
script.addEventListener('load',resolve);
script.addEventListener('error',reject);
(document.getElementsByTagName('head')[0] || document.body || document.documentElement).appendChild(script);
})
}
</br>
function domReady() {
return new Promise(function(resolve, reject) {
if(document.readyState === 'complete') {
resolve();
} else {
document.addEventListener('DOMContentLoaded',resolve);
}
})
}
</pre>
兼容性墊片polyfill of the ES6 Promise
參考資料: