ES6的promise對象研究
什么叫promise?
Promise對象可以理解為一次執行的異步操作,使用promise對象之后可以使用一種鏈式調用的方式來組織代碼;讓代碼更加的直觀。
那我們為什么要使用promise?
比如我們在工作中經常會碰到這么一個需求,比如我使用ajax發一個A請求后,成功后拿到數據,我們需要把數據傳給B請求;那么我們需要如下編寫代碼:
$.ajax({ url: '', dataType:'json', success: function(data) { // 獲取data數據 傳給下一個請求 var id = data.id; $.ajax({ url:'', data:{"id":id}, success:function(){ // ..... } }); } });
如上代碼;上面的代碼有如下幾點缺點:
- 后一個請求需要依賴於前一個請求成功后,將數據往下傳遞,會導致多個ajax請求嵌套的情況,代碼不夠直觀。
- 如果前后兩個請求不需要傳遞參數的情況下,那么后一個請求也需要前一個請求成功后再執行下一步操作,這種情況下,那么也需要如上編寫代碼,導致代碼不夠直觀。
因此針對這種情況下,我們可以使用promise對象來解決上面的問題了~
下面我們下來看看目前支持promise瀏覽器的情況如下:

支持的情況如上顯示;
如果我們想使用promise對象的話,我們可以使用中的promise對象,這個稍后慢慢來講解jquery中的promise對象;
我們先來看看ES6中的promise對象吧!
如何創建promise對象?
要想創建promise對象,可以使用new來調用promise的構造器來進行實例化。
如下代碼:
var promise = new Promise(function(resolve,reject){ // 異步處理 // 成功調用resolve 往下傳遞參數 且只接受一個參數 // 失敗調用reject 往下傳遞參數 且只接受一個參數 });
對通過new 生成的promise對象為了設置其值在resolve(成功) / reject(失敗) 時調用的回調函數,可以使用promise.then()實例方法。
如下代碼:
promise.then(onFulfilled, onRejected);
resolve(成功) 時 調用onFulfilled 方法,reject(失敗) 時 調用onRejected方法;
Promise.then 成功和失敗時都可以使用,如果出現異常的情況下可以采用
promise.then(undefined,onRejected) 這種方式,只指定onRejected回調函數即可,不過針對這種情況下我們有更好的選擇是使用catch這個方法;代碼如下:
promise.catch(onRejected);
上面啰嗦了這么多,我們來分別來學習相關的promise對象中的方法知識點吧!
理解Promise.resolve
一般情況下我們都會使用new Promise()來創建promise對象,但是我們也可以使用promise.resolve 和 promise.reject這兩個方法;
Promise.resolve(value)的返回值也是一個promise對象,我們可以對返回值進行.then調用;如下代碼:
Promise.resolve(11).then(function(value){ console.log(value); // 打印出11 });
resolve(11)代碼中,會讓promise對象進入確定(resolve狀態),並將參數11傳遞給后面的then所指定的onFulfilled 函數;
我們上面說過創建promise對象,可以使用new Promise的形式創建對象,但是我們這邊也可以使用Promise.resolve(value)的形式創建promise對象;
理解Promise.reject
Promise.reject 也是new Promise的快捷形式,也創建一個promise對象,比如如下代碼:
Promise.reject(new Error("我錯了,請原諒俺!!"));
就是下面的代碼new Promise的簡單形式:
new Promise(function(resolve,reject){ reject(new Error("我錯了,請原諒俺!!")); });
下面我們來綜合看看使用resolve方法和reject方法的demo如下:
function testPromise(ready) { return new Promise(function(resolve,reject){ if(ready) { resolve("hello world"); }else { reject("No thanks"); } }); }; // 方法調用 testPromise(true).then(function(msg){ console.log(msg); },function(error){ console.log(error); });
上面的代碼的含義是給testPromise方法傳遞一個參數,返回一個promise對象,如果為true的話,那么調用promise對象中的resolve()方法,並且把其中的參數傳遞給后面的then第一個函數內,因此打印出 “hello world”, 如果為false的話,會調用promise對象中的reject()方法,則會進入then的第二個函數內,會打印No thanks;
理解Promise異步調用的操作
如下代碼:
var promise = new Promise(function(resolve){ console.log(1); resolve(3); }); promise.then(function(value){ console.log(value); }); console.log(2);
上面的代碼輸出我們可以看到,分別為 1,2,3; 首先代碼從上往下執行,首先輸出1,然后調用resolve(3)這個方法,這時候promise對象變為確定狀態,即調用onFulFilled這個方法,從上面了解到,resolve(成功) 時 調用onFulfilled 方法,Promise.then 成功和失敗時都可以使用,因此第一個函數是成功調用的,但是Promise對象是以異步方式調用的,所以先執行console.log(2),輸出的是2,然后輸出的是3;
理解是同步調用還是異步調用
如下代碼:
function ready(fn){ var readyState = document.readyState; if (readyState === 'interactive' || readyState === 'complete') { fn(); } else { window.addEventListener('DOMContentLoaded', fn); } } ready(function(){ console.log("DOM Load Success"); }); console.log("我是同步輸出的");
如上代碼;如果在調用ready()方法之前DOM已經載入完成的話,就會對回調函數進行同步調用,先輸出DOM Load Success 后輸出 我是同步輸出的 文案;如果在調用ready()方法之前DOM為未載入完成的話,那么代碼先會執行 window.addEventListener('DOMContentLoaded', fn);
就會異步調用該函數,那么就會先輸出 “我是同步輸出的”,后輸出”DOM Load Success”;
為了解決上面的同步或者異步混亂的問題,我們現在可以使用promise對象使用異步的方式來解決;如下代碼
function readyPromise(){ return new Promise(function(resolve,reject){ var readyState = document.readyState; if (readyState === 'interactive' || readyState === 'complete') { resolve(); } else { window.addEventListener('DOMContentLoaded', resolve); } }); } readyPromise().then(function(){ console.log("DOM Load Success"); }); console.log("我是同步加載的,先執行我");
輸出如下:先輸出"我是同步加載的,先執行我" 后輸出 "DOM Load Success"。因為promise對象是異步加載的。
理解promise的三種狀態
Promise 對象有三種狀態:
Resolve 可以理解為成功的狀態;
Rejected 可以理解為失敗的狀態;
Pending既不是Resolve也不是Rejected狀態;可以理解為Promise對象實例創建時候的初始狀態;
比如Promise對象中的resolve方法就是調用then對象的第一個函數,也就是成功的狀態;而reject方法就是調用then對象的第二個函數,也就是失敗的狀態;
理解then()
上面的代碼,比如如下這樣的代碼就是then的列子;代碼如下:
function testPromise(ready) { return new Promise(function(resolve,reject){ if(ready) { resolve("hello world"); }else { reject("No thanks"); } }); }; // 方法調用 testPromise(true).then(function(msg){ console.log(msg); },function(error){ console.log(error); });
上面的代碼就是利用了 then(onFulfilled,onRejected)方法來執行的,第一個方法就是成功狀態的標志,第二個方法是失敗的狀態標志;
當然在多個任務的情況下then方法同樣可以使用;比如上面的代碼改成如下:
function testPromise(ready) { return new Promise(function(resolve,reject){ if(ready) { resolve("hello world"); }else { reject("No thanks"); } }); }; // 方法調用 testPromise(true).then(function(msg){ console.log(msg); }).then(testPromise2) .then(testPromise3); function testPromise2(){ console.log(2); } function testPromise3(){ console.log(3); }
輸出如下:hello world ,2,3
上面的代碼是then的鏈式調用方式,輸出是按順序輸出的 分別為 hello world , 2,3; 使用鏈式調用的原因是 每次調用后都會返回promise對象;
理解Promise.catch()方法
Promise.catch()方法是promise.then(undefined,onRejected)方法的一個別名,該方法用來注冊當promise對象狀態變為Rejected的回調函數。
如下代碼:
var promise = Promise.reject(new Error("message")); promise.catch(function(error){ console.log(error); });
打印如下所示:

理解每次調用then都會返回一個新創建的promise對象
不管是then還是catch方法調用,都返回一個新的promise對象;
下面我們來看看代碼如下:
var promise1 = new Promise(function(resolve){ resolve(1); }); var thenPromise = promise1.then(function(value){ console.log(value); }); var catchPromise = thenPromise.catch(function(error){ console.log(error); }); console.log(promise1 !== thenPromise); // true console.log(thenPromise !== catchPromise); //true
如上代碼,打印的都是true,這說明不管是then還是catch都返回了和新創建的promise是不同的對象;
如果我們知道了then方法每次都會創建返回一個新的promise對象的話,那么久不難理解下面的代碼了;如下:
var promise1 = new Promise(function(resolve){ resolve(1); }); promise1.then(function(value){ return value * 2; }); promise1.then(function(value){ return value * 2; }); promise1.then(function(value){ console.log("1"+value); });
如上的代碼;打印出11;因為他們每次調用then方法時,是使用的不同的promise對象;因此最后打印的value還是1;但是如果我們then方法是連續調用的話,那情況就不一樣了,比如如下代碼:
var promise1 = new Promise(function(resolve){ resolve(2); }); promise1.then(function(value){ return value * 2; }).then(function(value){ return value * 2; }).then(function(value){ console.log("1"+value); });
打印出18,即 "1" + 2*2*2 = 18;
上面第一種方法沒有使用方法鏈的調用,上面第一種那種寫法then 調用幾乎是同時開始進行的,且傳給每個then的value都是1;
第二種方式是使用方法鏈的then,使多個then方法連接在一起了,因此函數會嚴格執行 resolve -- then --- then -- then的順序執行,並且傳遞每個then方法的value的值都是前一個promise對象中return的值;因此最后的結果就是18了;
現在我們再回過頭一剛開始我們討論的為什么要使用promise的原因的問題了,比如2個ajax請求,后一個ajax請求需要獲取到前一個ajax請求的數據,我們之前在使用jquery寫代碼是如下的:
$.ajax({ url: '', dataType:'json', success: function(data) { // 獲取data數據 傳給下一個請求 var id = data.id; $.ajax({ url:'', data:{"id":id}, success:function(){ // ..... } }); } });
現在我們學習了then方法后,我們可以重新編寫上面的代碼變成如下:(代碼改成如下這樣的,2018-8-10更新的)
function ajaxPromise(url, data) { return new Promise(function(resolve, reject) { $.ajax({ url: url, contentType: 'json', data: data, success: function(resData) { resolve(resData); } }) }) } ajaxPromise('https://cnodejs.org/api/v1/topics', {}).then(function(res) { console.log(res); var id = res.data[0].id; return id; }).then(function(id) { console.log(1111); console.log(id); ajaxPromise('https://cnodejs.org/api/v1/topics' + id, {}); });
理解Promise.all
Promise.all可以接受一個元素為Promise對象的數組作為參數,當這個數組里面所有的promise對象都變為resolve時,該方法才會返回。
如下代碼:
var promise1 = new Promise(function(resolve){ setTimeout(function(){ resolve(1); },3000); }); var promise2 = new Promise(function(resolve){ setTimeout(function(){ resolve(2); },1000); }); Promise.all([promise1,promise2]).then(function(value){ console.log(value); // 打印[1,2] });
如上代碼 打印的是[1,2]; 如上我們看到promise1對象中的setTimeout是3秒的時間,而promise2對象中的setTimeout是1秒的時間,但是在Promise.all方法中會按照數組的原先順序將結果返回;
在我們平時的需求中,或許有這種情況的需求,比如我們需要發2個ajax請求時,不管他們的先后順序,當這2個ajax請求都同時成功后,我們需要執行某些操作的情況下,這種情況非常適合;
理解Promise.race
如上可知:Promise.all 在接收到的所有對象promise都變為FulFilled或者 Rejected狀態之后才會繼續后面的處理,但是Promise.race的含義是只要有一個promise對象進入FulFilled或者Rejected狀態的話,程序就會停止,且會繼續后面的處理邏輯;
如下代碼:
// `delay`毫秒后執行resolve function timerPromise(delay){ return new Promise(function(resolve){ setTimeout(function(){ resolve(delay); },delay); }); } // 任何一個promise變為resolve或reject 的話程序就停止運行 Promise.race([ timerPromise(1), timerPromise(32), timerPromise(64), timerPromise(128) ]).then(function (value) { console.log(value); // => 1 });
如上代碼創建了4個promise對象,這些promise對象分別在1ms,32ms,64ms,128ms后變為確定狀態,並且在第一個變為確定狀態后1ms后,then函數就會被調用,這時候resolve()方法給傳遞的值為1,因此執行then的回調函數后,值變為1;
我們再來看看當一個promise對象變為確定狀態(FulFiled)的時候,他們后面的promise對象是否還在運行呢?我們繼續看如下代碼運行:
var runPromise = new Promise(function(resolve){ setTimeout(function(){ console.log(1); resolve(2); },500); }); var runPromise2 = new Promise(function(resolve){ setTimeout(function(){ console.log(3); resolve(4); },1000); }); // 第一個promise變為resolve后程序停止 Promise.race([runPromise,runPromise2]).then(function(value){ console.log(value); });
如上代碼是使用定時器調用的,上面是2個promise對象,我們看到第一個promise對象過500毫秒后加入到執行隊列里面去,如果執行隊列沒有其他線程在運行的時候,就執行該定時器,所以第一次打印1,然后調用resolve(2); 接着調用promise.race方法,該方法只要有一個變為成功狀態(FulFiled)的時候,程序就會停止,因此打印出2,同時后面的promise對象接着執行,因此打印出3,但是由於promise.race()該方法已經停止調用了,所以resolve(4)不會有任何輸出;因此最后輸出的是1,2,3;
由此我們得出結論,當一個promise對象變為(FulFilled)成功狀態的時候,后面的promise對象並沒有停止運行。
Deferred和Promise的關系
Deferred 包含 Promise;
Deferred具備Promise的狀態進行操作的特權方法;
下面我們來看看使用promise來實現deferred;如下代碼:
function Deferred(){ this.promise = new Promise(function(resolve,reject){ this._resolve = resolve; this._reject = reject; }.bind(this)); } Deferred.prototype.resolve = function(value) { this._resolve.call(this.promise,value); }; Deferred.prototype.reject = function(reason) { this._reject.call(this.promise,reason); }; function getURL(URL){ var deferred = new Deferred(); var req = new XMLHttpRequest(); req.open('GET',URL,true); req.onload = function(){ if(req.status === 200) { deferred.resolve(req.responseText); }else { deferred.reject(new Error(req.statusText)); } }; req.onerror = function(){ deferred.reject(new Error(req.statusText)); }; req.send(); return deferred.promise; } var URL = 'http://127.0.0.1/promise/promise.php'; getURL(URL).then(function onFulfilled(value){ console.log(value); });
其中promise.php代碼輸出的是一個json的數據,代碼如下:
<?php $data = json_decode(file_get_contents("php://input")); header("Content-Type: application/json; charset=utf-8"); echo ('{"id" : ' . $data->id . ', "age" : 24, "sex" : "boy", "name" : "huangxueming"}'); ?>
最后執行打印console的出來是:
{"id" : , "age" : 24, "sex" : "boy", "name" : "huangxueming"}
使用promise封裝deferred的方法,無非就是使用promise對象中的resolve和Reject等調用方法,下面我們再來看看使用promise對象對ajax請求的封裝如下:
function getURL(URL){ return new Promise(function (resolve, reject) { var req = new XMLHttpRequest(); req.open('GET', URL, true); req.onload = function () { if (req.status === 200) { resolve(req.responseText); } else { reject(new Error(req.statusText)); } }; req.onerror = function () { reject(new Error(req.statusText)); }; req.send(); }); } var URL = 'http://127.0.0.1/promise/promise.php'; getURL(URL).then(function onFulfilled(value){ console.log(value); });
上面分別兩種方式使用promise對象實現ajax請求的封裝對比如下:
Deferred那種方式不需要將promise代碼括起來。
Promise代表了一個對象,這個對象的狀態現在還不確定,但是未來一個時間點它的狀態要么變為正常值(FulFilled),要么變為異常值(Rejected);而Deferred對象表示了一個處理還沒有結束的這種事實,在它的處理結束的時候,可以通過Promise來取得處理結果。
