引言 Promise 是異步編程的一種解決方案,比傳統的解決方案——回調和事件——更合理且強大。最近的項目要用到這個,就參照阮一峯老師的《ES6標準入門》這本書簡單學一下了。javascript
所謂 Promise ,簡單來講就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。從語法上來看,Promise 是一個對象,從它能夠獲取異步操做的消息。Promise 提供統一的 API,各類異步操做均可以用一樣的方法進行處理。java
Promise 對象有如下兩個特色。es6
ES6 規定,Promise 對象是一個構造函數,用來生成 Promise 實例。
舉個例子。ajax
var promise = new Promise(function (resolve, reject) { // some code if (/* 異步操做成功*/) { resolve(value); } else { // 異步操做失敗 reject(error); } });
Promise 構造函數接收一個函數
做爲參數,該函數
的兩個參數分別是 resolve
和 reject
。他們是兩個函數,由 Javascript 引擎提供,不用本身部署。編程
resolve
函數的做用是將 Promise
對象的狀態從『未完成』(Pending)變爲『成功』(Resolved),在異步操做成功的時候調用,並將異步操做的結果做爲參數傳遞過去。 reject
函數的做用是,將 Promise
對象的狀態從『未完成』(Pending)變爲『失敗』(Rejected)json
當咱們生成了一個 Promise 實例以後。就能夠用 then
方法分別指定 Resolved
狀態和 Rejected
狀態的回調函數。數組
promise.then(function (value) { // success console.log(value); }, function (error) { // failed console.log(error); });
then
方法能夠接受兩個回調函數做爲參數。第一個回調函數是 Promise
對象的狀態變爲 Resolved
時調用,第二個回調函數是 Promise
對象的狀態變爲 Rejected
時調用。其中第二個參數是可選的,不必定要提供。這兩個函數都接收 Promise
對象傳出的值做爲參數。promise
咱們來個小例子異步
let promise = new Promise(function(resolve,reject){ console.log('Promise'); let value = 'value'; resolve(value); }); promise.then(function(value){ console.log(value); }); console.log('Hi'); // Promise // Hi // value
上面的代碼中,Promise 新建後會當即執行,因此首先輸出的是 Promise
。而後,then
方法指定的回調函數將當前腳本全部同步任務執行完成後纔會執行,因此 Resolved
最後輸出。異步編程
Promise 實例具備 then
方法,即 then
方法是定義在原型對象 Promise.prototype
上的。它的做用是爲 Promise 實例添加改變狀態時的回調函數。前面說過,then
方法的第一個參數是 Resolved
狀態的回調函數,第二個參數(可選)是 Rejected
狀態的回調函數。
then
方法返回的是一個新的 Promise 實例
(注意
不是原來的那個 Promise 實例)。所以能夠採用鏈式寫法,即 then
方法後面再調用另外一個 then
方法。
getJSON("/posts.json").then(function(json) { return json.post; }).then(function(post) { // ... });
上面的代碼使用 then
方法依次指定了兩個回調函數。第一個回調函數完成之後,將會返回結果做爲參數,傳入第二個回調函數。
而後採用鏈式的 then
能夠指定一組按順序調用的回調函數。這時,前一個回調函數有可能返回的仍是一個 Promise 對象(即有異步操做),然後一個回調函數就會等待該 Promise 對象的狀態發生變化,再被調用。
Promise.prototype.catch
方法是 .then(null, rejection)
的別名,用於指定發生錯誤時的回調函數。
getJSON('/posts.json').then(function(posts) { // ... }).catch(function(error) { // 處理 getJSON 和 前一個回調函數運行時發生的錯誤 console.log('發生錯誤!', error); });
上面的代碼中,getJSON
方法返回一個 Promise 對象,若是該對象狀態變爲 Resolved
,則會調用 then
方法指定的回調函數;若是異步操做拋出錯誤,狀態就會變成 Rejected
,而後調用 catch
方法指定的回調函數處理這個錯誤。另外, then
方法指定的回調函數若是在運行中拋出錯誤,也會被 catch
方法捕獲。
p.then((val) => console.log('fulfilled:', val)) .catch((err) => console.log('rejected', err)); // 等同於 p.then((val) => console.log('fulfilled:', val)) .then(null, (err) => console.log("rejected:", err));
下面是一個例子。
const promise = new Promise(function(resolve, reject) { throw new Error('test'); }); promise.catch(function(error) { console.log(error); }); // Error: test
上面的代碼中,Promise 拋出一個錯誤就被 catch
方法指定的回調函數所捕獲。注意,上面的寫法和下面兩種寫法是等價的。
// 寫法一 const promise = new Promise(function(resolve, reject) { try { throw new Error('test'); } catch(e) { reject(e); } }); promise.catch(function(error) { console.log(error); }); // 寫法二 const promise = new Promise(function(resolve, reject) { reject(new Error('test')); }); promise.catch(function(error) { console.log(error); });
由上面能夠看出, reject
方法的做用等同於拋出錯誤。
若是 Promise
狀態已經變成 Resolved,在拋出錯誤是無效的。
const promise = new Promise(function(resolve, reject) { resolve('ok'); // Promise 狀態已變成 已完成 throw new Error('test'); }); promise .then(function(value) { console.log(value) }) .catch(function(error) { console.log(error) }); // ok
注意 通常來講,不要在 then
方法中定義 Reject
狀態的回調函數(即 then
的第二個參數),而是使用 catch
方法。
// bad promise .then(function(data) { // success }, function(err) { // error }); // good promise .then(function(data) { //cb // success }) .catch(function(err) { // error });
上面代碼中,第二種寫法要好於第一種寫法,理由是第二種寫法能夠捕獲前面 then
方法執行中的錯誤,也更接近同步的寫法(try/catch)。所以,建議老是使用 catch
方法,而不使用 then
方法的第二個參數。
Promise.all
方法是將多個 Promise 對象實例包裝成一個新的實例。
var p = Promise.all([p1, p2, p3]);
上面的代碼中,Promise.all()
方法接受一個數組做爲參數,p1, p2, p3 都是 Promise 對象的實例。若是不是,就會先調用下面講到的 Promise.resolve
方法,將參數轉換爲 Promise 實例,再進一步處理(Promise.all
方法的參數不必定是數組,可是必須具備 Iterator
接口,且每一個返回成員都是 Promise 實例)。
p 的狀態由 p1, p2, p3 決定,分紅兩種狀況
下面是一個具體例子。
var promises = [2,3,4,5].map(function(id){ console.log(id) }); Promise.all(promises).then(function(res){ console.log(res); resolve }).catch(function(error){ console.log(error); }); // 先執行全部 promise 實例的異步操做,而後吧操做的結果打包數組返回 // 2 3 4 5 [undefined,undefined,undefined,undefined]
上面的代碼中,Promise 是包含 6 個 Promise 實例的數組,只有這 6 個實例的狀態都變成 fulfilled,或者其中有 1 個變成 rejected,纔會調用 Promise.all
方法後面的回調函數。
Promise.race
方法一樣是將多個 Promise 實例包裝成一個新的 Promise 實例。
var p = Promise.race([p1, p2, p])
上面的代碼中,只要 p1, p2, p3 中 有一個實例
率先改變狀態,p 的狀態就跟着改變。那個率先改變的 Promise 實例的返回值就傳遞給 p 的回調函數。Promise.race
方法的參數與 Promise.all
方法同樣,若是不是 Promise 實例,就會先調用下面講到的 Promise.resolve
方法,將參數轉爲 Promise 實例,再進一步處理。
下面是一個例子,若是指定時間內沒有得到結果,就將 Promise 的狀態變成 Rejected,不然就變爲 Resolved。
const p = Promise.race([ fetch('/resource-that-may-take-a-while'), new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('request timeout')), 5000) }) ]); p .then(console.log) .catch(console.error);
上面代碼中,若是 5 秒以內 fetch
方法沒法返回結果,變量 p 的狀態就會變爲 rejected,從而觸發 catch
方法指定的回調函數。
有時須要將現有對象轉爲 Promise 對象,Promise.resolve
方法就起到這個做用。
const jsPromise = Promise.resolve($.ajax('/whatever.json'));
上面代碼將 jQuery 生成的 deferred 對象,轉爲一個新的 Promise 對象。
Promise.resolve
等價於下面的寫法。
Promise.resolve('foo') // 等價於 new Promise(resolve => resolve('foo'))
Promise.resolve方法的參數分紅四種狀況。
若是參數是一個 Promise 實例,那麼 Promise.resolve
將不作任何修改,原封不動的返回這個實例。
thenable 對象是指具備 then 方法的對象,例以下面這個對象
let thenable = { then: function(resolve, reject) { resolve(42); } };
Promise.resolve
方法會將這個對象轉爲 Promise 對象,任何執行 thenable 對象的 then 方法。
let thenable = { then: function(resolve, reject) { resolve(42); } }; let p1 = Promise.resolve(thenable); p1.then(function(value) { console.log(value); // 42 });
上面的代碼中, thenable 對象的 then 方法執行後,對象 p1 的狀態就變爲 resolved,從而當即執行最後的 then 方法指定的回調函數。輸出 42。
若是參數是一個原始值,或者是一個不具備 then 方法的對象,那麼 Promise.resolved
方法返回一個新的 Promise 對象,狀態爲 Resolved。
const p = Promise.resolve('Hello'); p.then(function (s){ console.log(s) }); // Hello
上面代碼生成一個新的 Promise 對象的實例 p。因爲字符串 Hello 不屬於異步操做(判斷方法是字符串對象不具備 then 方法),返回 Promise 實例的狀態從一輩子成就是 resolved,因此回調函數會當即執行。Promise.resolve
方法的參數,會同時傳給回調函數。
Promise.resolved
方法容許在調用時不帶有參數,而直接返回一個 Resolved 狀態的 Promise 對象。
因此,若是你但願獲得一個 Promise 對象,比較方便的方法就是直接調用 Promise.resolve
方法。
const p = Promise.resolve(); p.then(function () { // ... });
上面代碼中的 p 就是一個 Promise 對象。
須要注意的是,當即 resolve 的 Promise 對象實在本輪 『事件循環』(event loop)結束時,而不是在下一輪『事件循環』開始時。
setTimeout(function () { console.log('three'); }, 0); Promise.resolve().then(function () { console.log('two'); }); console.log('one'); // one // two // three
上面代碼中,setTimeout(fn, 0)
在下一輪『事件循環』開始時執行,Promise.resolve()
在本輪『事件循環』結束時執行,console.log('one')
則是當即執行,所以最早輸出。
Promise.reject(resson)
方法也會返回一個新的 Promise 實例,狀態爲 Rejected (這個就暫時想不懂怎麼應用了)。
const p = Promise.reject('出錯了'); // 等同於 const p = new Promise((resolve, reject) => reject('出錯了')) p.then(null, function (s) { console.log(s) }); // 出錯了
上面的代碼生成一個 Promise 對象的實例 p,狀態爲 Rejected,回調函數會當即執行。
以上就是關於 Promise 學習的內容,若有錯誤的地方就請在下面評論處,發表一下見解,固然也能夠放一下關於進階學習 Promise 的文章,你們一塊兒學習。