所謂Promise
,簡單說就是一個容器,里面保存着某個未來才會結束的事件(通常是一個異步操作)的結果。從語法上說,Promise是一個對象,從它可以獲取異步操作的消息。Promise提供統一的API,各種異步操作都可以用同樣的方法進行處理。
Promise
對象有以下兩個特點。
(1)對象的狀態不受外界影響。Promise
對象代表一個異步操作,有三種狀態:Pending
(進行中)、Resolved
(已完成,又稱Fulfilled)和Rejected
(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise
這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變。
(2)一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise
對象的狀態改變,只有兩種可能:從Pending
變為Resolved
和從Pending
變為Rejected
。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果。就算改變已經發生了,你再對Promise
對象添加回調函數,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。
Promise
也有一些缺點。首先,無法取消Promise
,一旦新建它就會立即執行,無法中途取消(發起 fetch 請求,不能 abort。fetch 是基於 Promise 設計)。其次,如果不設置回調函數,Promise
內部拋出的錯誤,不會反應到外部。第三,當處於Pending
狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。
如果某些事件不斷地反復發生,一般來說,使用stream模式是比部署Promise
更好的選擇。
var promise = new Promise(function(resolve, reject) { // 進行異步操作 if (/* 異步操作成功 */){ resolve(value); // 調用 resolve,將異步操作返回的 value,作為參數傳遞出去 } else { reject(error); // 調用 reject,將異步操作返回的 error,作為參數傳遞出去 } });
promise.then(function(value) { // 接收 resolve 傳遞的參數,進行處理 }, function(error) { // 接收 reject 傳遞的參數,進行處理。第二個參數為可選的 });
在新建 Promise 的時候,new Promise 里的代碼會立即執行:
let promise = new Promise(function(resolve, reject) { // new Promise 新建了 Promise,這里面的代碼立即執行 console.log('Promise'); resolve(); }); // then 方法的回調函數是異步操作,所以會添加到事件循環的任務隊列中,主線程操作完畢后才執行 promise.then(function() { console.log('Resolved.'); }); console.log('Hi!'); // Promise // Hi! // Resolved
Promise 封裝 AJAX
var getJSON = function(url) { var promise = new Promise(function(resolve, reject){ var client = new XMLHttpRequest(); client.open("GET", url); client.onreadystatechange = handler; client.responseType = "json"; client.setRequestHeader("Accept", "application/json"); client.send(); function handler() { if (this.readyState !== 4) { return; } if (this.status === 200) { // 使用 resolve 得到 AJAX 獲取的數據 resolve(this.response); } else { // 使用 reject 拋出錯誤 reject(new Error(this.statusText)); } }; }); return promise; }; getJSON("/posts.json").then(function(json) { console.log('Contents: ' + json); }, function(error) { console.error('出錯了', error); });
Promise.prototype.then()
then
方法返回的是一個新的Promise實例(注意,不是原來那個Promise實例)。因此可以采用鏈式寫法,即then
方法后面再調用另一個then
方法。
getJSON("/posts.json").then(function(json) { return json.post; }).then(function(post) { // 第二個 then 方法中,函數接收的參數是前一個 then 方法中的返回值 // ... });
getJSON("/post/1.json").then(function(post) { // 在 then 方法中調用 getJSON 返回了另一個 Promise 對象 return getJSON(post.commentURL); }).then(function funcA(comments) { // 這個 then 方法就會根據前一個 then 方法返回的 Promise 對象的狀態,發生變化后,調用 funcA 或 funcB console.log("Resolved: ", comments); }, function funcB(err){ console.log("Rejected: ", err); });
Promise.prototype.catch()
Promise.prototype.catch
方法是.then(null, rejection)
的別名,用於指定發生錯誤時的回調函數。
getJSON("/posts.json").then(function(posts) { // ... }).catch(function(error) { // 處理 getJSON 和 前一個回調函數運行時發生的錯誤 console.log('發生錯誤!', error); });
Promise對象的錯誤具有“冒泡”性質,會一直向后傳遞,直到被捕獲為止。也就是說,錯誤總是會被下一個catch
語句捕獲。
一般來說,不要在then
方法里面定義Reject狀態的回調函數(即then
的第二個參數),總是使用catch
方法。
需要注意的是,catch
方法返回的還是一個Promise對象,因此后面還可以接着調用then
方法。
var someAsyncThing = function() { return new Promise(function(resolve, reject) { // 下面一行會報錯,因為x沒有聲明 resolve(x + 2); }); }; someAsyncThing() .catch(function(error) { console.log('oh no', error); }) .then(function() { console.log('carry on'); }); // oh no [ReferenceError: x is not defined] // carry on
上面代碼運行完catch
方法指定的回調函數,會接着運行后面那個then
方法指定的回調函數。如果沒有報錯,則會跳過catch
方法。
Promise.all()
Promise.all
方法用於將多個Promise實例,包裝成一個新的Promise實例。
var p = Promise.all([p1, p2, p3]); // p1, p2, p3 都是 Promise 對象實例
p
的狀態由p1
、p2
、p3
決定,分成兩種情況。
(1)只有p1
、p2
、p3
的狀態都變成fulfilled
,p
的狀態才會變成fulfilled
,此時p1
、p2
、p3
的返回值組成一個數組,傳遞給p
的回調函數。
(2)只要p1
、p2
、p3
之中有一個被rejected
,p
的狀態就變成rejected
,此時第一個被reject
的實例的返回值,會傳遞給p
的回調函數。
// 生成一個Promise對象的數組 var promises = [2, 3, 5, 7, 11, 13].map(function (id) { return getJSON("/post/" + id + ".json"); }); Promise.all(promises).then(function (posts) { // promises 全部 resolved 的情況,走這里 }).catch(function(reason){ // promises 中有 rejected 的情況,走這里 });
Promise.race()
var p = Promise.race([p1,p2,p3]); // p1, p2, p3 都是 Promise 對象實例
只要p1
、p2
、p3
之中有一個實例率先改變狀態,p
的狀態就跟着改變。那個率先改變的Promise實例的返回值,就傳遞給p
的回調函數。
done()
asyncFunc() .then(f1) .catch(r1) .then(f2) .done(onFulfilled, onRejected);
done
方法的使用,可以像then
方法那樣用,提供Fulfilled
和Rejected
狀態的回調函數,也可以不提供任何參數。但不管怎樣,done
都會捕捉到任何可能出現的錯誤,並向全局拋出。
finally()
server.listen(0) .then(function () { // run test }) .finally(server.stop);
finally
方法用於指定不管Promise對象最后狀態如何,都會執行的操作。它與done
方法的最大區別,它接受一個普通的回調函數作為參數,該函數不管怎樣都必須執行。