Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最先提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了 Promise 對象。前端
Promise 對象是一個代理對象(代理一個值),被代理的值在 Promise 對象建立時多是未知的。它容許你爲異步操做的成功和失敗分別綁定相應的處理方法(handlers)。 這讓異步方法能夠像同步方法那樣返回值,但並非當即返回最終執行結果,而是一個能表明將來出現的結果的 Promise 對象。 Promise 對象有如下兩個特色:ajax
對象的狀態不受外界影響。Promise 對象表明一個異步操做,有三種狀態:pending(進行中)、fulfilled(已成功) 和 rejected(已失敗)。只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是「承諾」,表示其餘手段沒法改變。 Promise 對象的狀態改變,只有兩種可能:從 pending 變爲 fulfilled 和 從 pending 變爲 rejected。只要這兩種狀況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱爲 resolved(已定型)。若是改變已經發生了,你再對 Promise 對象添加回調函數,也會當即獲得這個結果。這與事件(Event)徹底不一樣,事件的特色是,若是你錯過了它,再去監聽,是得不到結果的。 基本用法編程
new Promise( function(resolve, reject) {...} /* executor */ );
Promise 對象的初始化接收一個執行函數 executor,executor 是帶有 resolve 和 reject 兩個參數的函數 。數組
Promise 構造函數執行時會當即調用 executor 函數, resolve 和 reject 兩個函數做爲參數傳遞給 executor(executor 函數在 Promise 構造函數返回新建對象前被調用)。promise
resolve 和 reject 函數被調用時,分別將 promise 的狀態改成 fulfilled(完成) 或 rejected(失敗)。executor 內部一般會執行一些異步操做,一旦完成,能夠調用 resolve 函數來將 promise 狀態改爲 fulfilled,或者在發生錯誤時將它的狀態改成 rejected。異步
若是在 executor 函數中拋出一個錯誤,那麼該 promise 狀態爲 rejected。executor函數的返回值被忽略。異步編程
先看個示例:(注:後文的示例均使用 setTimeout 模擬異步操做)函數
// 從 pending 變爲 fulfilled var p = new Promise(function(resolve, reject) { setTimeout(function() { console.log('Hi,'); resolve('promise fulfilled!'); }, 500); }).then(function(data) { console.log(data); }); // Hi, // promise fulfilled! // 從 pending 變爲 rejected var p = new Promise(function(resolve, reject) { setTimeout(function() { console.log('Hi,'); reject('promise rejected!'); }, 500); }).then(null, function(error) { console.log(error); //歡迎加入全棧開發交流圈一塊兒學習交流:864305860 }); //面向1-3年前端人員 // Hi, //幫助突破技術瓶頸,提高思惟能力 // promise rejected!
從 pending 變爲 fulfilled 這段代碼,當執行 new Promise() 時,傳入的執行函數就當即執行了,此時其內部有一個異步操做(過 500ms 以後執行),等過了 500ms 以後先執行 console.log('Hi,'); 輸出 Hi,,此時 promise 的狀態爲 pending(進行中),而執行 resolve('Promise!'); 則修改 promise 的狀態爲 fulfilled(完成),而後咱們調用 then() 接收 promise 在 fulfilled 狀態下傳遞的值,此時輸出 'Promise!'。學習
同理,從 pending 變爲 rejected 這段代碼基本差很少,不一樣的是異步操做調用了 reject 方法,then 方法使用第二個參數接收 rejected 狀態下傳遞的值。url
Promise.prototype.then()
then 的做用是爲 Promise 實例添加狀態改變時的回調函數。
then 方法的第一個參數是 resolved 狀態的回調函數,第二個參數(可選)是 rejected 狀態的回調函數。
var p = new Promise(function(resolve, reject) { setTimeout(function() { console.log('Hi,'); // 模擬請求,請求狀態爲200表明成功,不是200表明失敗 if (status === 200) { resolve('promise fulfilled!'); } else { reject('promise rejected!'); } }, 500); }).then(function(data) { console.log(data); }, function(error) { console.log(error); }); // 若是調用 resolve 方法,輸出以下: // Hi, // promise fulfilled! // 若是調用 reject 方法,輸出以下: // Hi, // promise rejected!
then 方法返回的是一個新的 Promise 實例(注意,不是原來那個Promise實例)。所以能夠採用鏈式寫法,即 then 方法後面再調用另外一個 then 方法。採用鏈式的 then,能夠指定一組按照次序調用的回調函數。這時,前一個回調函數,有可能返回的仍是一個 Promise 對象(即有異步操做),這時後一個回調函數,就會等待該 Promise 對象的狀態發生變化,纔會被調用。
var p = new Promise(function(resolve, reject) { setTimeout(function() { console.log('Hi,'); resolve(); }, 500); }).then(function() { return new Promise(function(resolve, reject) { setTimeout(function() { // 模擬請求,請求狀態爲200表明成功,不是200表明失敗 if (status === 200) { resolve('promise fulfilled!'); } else { reject('promise rejected!'); } }); }) }).then(function(data) { console.log(data); //歡迎加入全棧開發交流圈一塊兒學習交流:864305860 }, function(error) { //面向1-3年前端人員 console.log(error); //幫助突破技術瓶頸,提高思惟能力 }); // 若是第一個 then 調用 resolve 方法,第二個 then 調用第一個回調函數,最終輸出以下: // Hi, // promise fulfilled! // 若是第一個 then 調用 reject 方法,第二個 then 調用第一個回調函數,最終輸出以下: // Hi, // promise rejected! Promise.prototype.catch()
catch 方法是.then(null, rejection)的別名,用於指定發生錯誤時的回調函數。
因此下面代碼:
var p = new Promise(function(resolve, reject) { setTimeout(function() { console.log('Hi,'); // 模擬請求,請求狀態爲200表明成功,不是200表明失敗 if (status === 200) { resolve('promise fulfilled!'); } else { reject('promise rejected!'); } }, 500); }).then(function(data) { console.log(data); }, function(error) { console.log(error); });
等價於:
var p = new Promise(function(resolve, reject) { setTimeout(function() { console.log('Hi,'); // 模擬請求,請求狀態爲200表明成功,不是200表明失敗 if (status === 200) { resolve('promise fulfilled!'); } else { reject('promise rejected!'); } }, 500); }).then(function(data) { console.log(data); }).catch(function(error) { console.log(error); });
若是沒有使用 catch 方法或者 then 第二個參數指定錯誤處理的回調函數,Promise 對象拋出的錯誤不會傳遞到外層代碼,即不會有任何反應,這跟傳統的 try/catch 代碼塊是不一樣。
catch 方法返回的仍是一個 Promise 對象,所以後面還能夠接着調用 then 方法。
catch 方法與 .then(null, rejection) 的不一樣:
若是異步操做拋出錯誤,狀態就會變爲 rejected,就會調用 catch 方法指定的回調函數,處理這個錯誤。 then 方法指定的回調函數,若是運行中拋出錯誤,也會被 catch 方法捕獲。 catch 方法的寫法更接近同步的寫法(try/catch)。 所以,建議老是使用 catch 方法,而不使用 then 方法的第二個參數。
Promise.prototype.finally()
finally 方法用於指定無論 Promise 對象最後狀態如何,都會執行的操做。該方法是 ES2018 引入標準的。
var p = new Promise(function(resolve, reject) { setTimeout(function() { console.log('Hi,'); // 模擬請求,請求狀態爲200表明成功,不是200表明失敗 if (status === 200) { resolve('promise fulfilled!'); } else { reject('promise rejected!'); } }, 500); }).then(function(data) { console.log(data); }).catch(function(error) { console.log(error); }).finally(function() { console.log('I am finally!'); });
上面代碼中,無論 promise 最後的狀態,在執行完 then 或 catch 指定的回調函數之後,都會執行 finally 方法指定的回調函數。
Promise.all()
Promise.all 方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。
var p = Promise.all([p1, p2]);
上面代碼中,Promise.all 方法接受一個數組做爲參數,p一、p2 都是 Promise 實例,若是不是,就會先調用下面講到的 Promise.resolve 方法,將參數轉爲 Promise 實例,再進一步處理。(Promise.all方法的參數能夠不是數組,但必須具備 Iterator 接口,且返回的每一個成員都是 Promise 實例。)
p的狀態由p一、p2決定,分紅兩種狀況。
只有 p一、p2 的狀態都變成 fulfilled,p 的狀態纔會變成 fulfilled,此時 p一、p2 的返回值組成一個數組,傳遞給 p 的回調函數。
只要 p一、p2 之中有一個被 rejected,p 的狀態就變成 rejected,此時第一個被 reject 的實例的返回值,會傳遞給 p 的回調函數。
示例:
試想一個頁面聊天系統,咱們須要從兩個不一樣的 URL 分別得到用戶的我的信息和好友列表,這兩個任務是能夠並行執行的,用Promise.all()實現。
// 並行執行異步任務 var p1 = new Promise(function (resolve, reject) { setTimeout(function() { // 模擬請求,請求狀態爲200表明成功,不是200表明失敗 if (status === 200) { resolve('P1'); } else { reject('error'); } }, 500); }); var p2 = new Promise(function (resolve, reject) { setTimeout(resolve, 600, 'P2'); }); // 同時執行p1和p2,並在它們都完成後執行then: Promise.all([p1, p2]).then(function (results) { console.log(results); // 輸出:['P1', 'P2'] }).catch(function(error) { console.log(error); // 若是p1執行失敗,則輸出:error });
注意,若是做爲參數的 Promise 實例,本身定義了 catch 方法,那麼它一旦被 rejected,並不會觸發 Promise.all() 的 catch 方法。
Promise.race()
Promise.race 方法一樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。
var p = Promise.race([p1, p2]);
上面代碼中,只要 p一、p2 之中有一個實例率先改變狀態,p 的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給 p 的回調函數。
Promise.race 方法的參數與 Promise.all 方法同樣,若是不是 Promise 實例,就會先調用下面講到的 Promise.resolve 方法,將參數轉爲 Promise 實例,再進一步處理。
示例:
有些時候,多個異步任務是爲了容錯。好比,同時向兩個 URL 讀取用戶的我的信息,只須要得到先返回的結果便可。這種狀況下,用Promise.race()實現。
// 多任務容錯 var p1 = new Promise(function (resolve, reject) { setTimeout(resolve, 500, 'P1'); }); var p2 = new Promise(function (resolve, reject) { setTimeout(resolve, 400, 'P2'); }); Promise.race([p1, p2]).then(function (result) { console.log(result); // 'P2' }); Promise.resolve()
有時須要將現有對象轉爲 Promise 對象,Promise.resolve 方法就起到這個做用。
Promise.resolve方法的參數分紅四種狀況:
(1)參數是一個 Promise 實例
若是參數是 Promise 實例,那麼Promise.resolve將不作任何修改、原封不動地返回這個實例。
(2)參數是一個 thenable 對象
thenable 對象指的是具備 then 方法的對象,好比下面這個對象。
var thenable = { then: function (resolve, reject) { resolve(42); } };
Promise.resolve 方法會將這個對象轉爲 Promise 對象,而後就當即執行 thenable 對象的 then 方法。
var thenable = { then: function (resolve, reject) { resolve(42); } }; var p1 = Promise.resolve(thenable); p1.then(function (value) { console.log(value); // 42 });
上面代碼中,thenable 對象的 then 方法執行後,對象 p1 的狀態就變爲 resolved,從而當即執行最後那個 then 方法指定的回調函數,輸出 42。
(3)參數不是具備 then 方法的對象,或根本就不是對象
若是參數是一個原始值,或者是一個不具備 then 方法的對象,則 Promise.resolve 方法返回一個新的 Promise 對象,狀態爲 resolved。
var p = Promise.resolve('Hello'); p.then(function (s) { console.log(s) }); // 'Hello' var p1 = Promise.resolve(true); p1.then(function (b) { console.log(b) }); // true var p2 = Promise.resolve(1); p1.then(function (n) { console.log(n) }); // 1
(4)不帶有任何參數
Promise.resolve 方法容許調用時不帶參數,直接返回一個 resolved 狀態的 Promise 對象。
因此,若是但願獲得一個 Promise 對象,比較方便的方法就是直接調用 Promise.resolve 方法。
Promise.reject() Promise.reject 方法也會返回一個新的 Promise 實例,該實例的狀態爲 rejected。
注意,Promise.reject 方法的參數,會原封不動地做爲 reject 的參數,變成後續方法的參數。這一點與 Promise.resolve 方法不一致。
var thenable = { then(resolve, reject) { reject('出錯了'); } }; Promise.reject(thenable) .catch(e = > { console.log(e === thenable) }) // true
上面代碼中,Promise.reject 方法的參數是一個 thenable 對象,執行之後,後面 catch 方法的參數不是 reject 拋出的 出錯了 這個字符串,而是 thenable 對象。
加載圖片
咱們能夠將圖片的加載寫成一個 Promise,一旦加載完成,Promise 的狀態就發生變化。
function (path) { return new Promise(function (resolve, reject) { const image = new Image(); image.onload = resolve; image.onerror = reject; image.src = path; }); };
封裝ajax
咱們能夠將 ajax 請求寫成一個 Promise,根據請求的不一樣狀態改變 Promise 的狀態。
function ajax(method, url, data) { var request = new XMLHttpRequest(); return new Promise(function (resolve, reject) { request.onreadystatechange = function () { if (request.readyState === 4) { if (request.status === 200) { resolve(request.responseText); } else { reject(request.status); } //歡迎加入全棧開發交流圈一塊兒學習交流:864305860 } //面向1-3年前端人員 }; //幫助突破技術瓶頸,提高思惟能力 request.open(method, url); request.send(data); }); }
總結 優勢:
能夠將異步操做以同步操做的流程表達出來,避免了層層嵌套的回調函數(回調地獄)。 在異步執行的流程中,能夠把執行代碼和處理結果的代碼清晰地分離開來。
缺點:
沒法取消 Promise,一旦新建它就會當即執行,沒法中途取消。 若是不設置回調函數,Promise 內部拋出的錯誤,不會反應到外部。 當處於 pending 狀態時,沒法得知目前進展到哪個階段(剛剛開始仍是即將完成)。