在異步編程中,Promise扮演了舉足輕重的角色,它解決了ajax請求過程當中的回調地獄的問題,令代碼更具可讀性。下面的介紹中,筆者會經過一些片斷代碼,加上一些其本身的理解帶你們一塊兒從新溫故一下Promise爲編程所帶來的便利。javascript
Promise是抽象異步處理對象以及對其進行各類操做的組件;vue
Promise很重要!很重要!很重要!對,要強調三遍,必定要好好掌握。java
實例(假如此處你還不是很瞭解,不要緊,先留個印象):react
var promise = new Promise((resolve,reject) => {
if(true) { resolve(100) };
if(false) { reject('error') };
});
//使用
promise.then(value => {
console.log(value); //100
}).catch(error => {
console.error(error);
});
複製代碼
**注意⚠️*本文中的函數表達式均採用ES6的箭頭函數表達式的語法,你若還不是很清楚,請自行查閱(參考阮一峯ECMAScript 6入門)[1]。es6
剛剛咱們說,Promise解決了ajax請求過程當中的回調地獄的問題,那麼回調(函數)是什麼,爲何要用到回調(函數),咱們一塊兒回顧一下。ajax
回調函數(callback function),也被稱做高階函數。編程
就是把一個函數B做爲參數(注意:是做爲參數)傳入「另外一個函數A」中,而後這個函數B在「另外一個函數A」中調用,那麼這個函數B,就叫回調函數。函數A執行完之後執行函數B,這個過程就叫作回調。json
注意:回調函數不是當即就執行。它是在另外一個函數執行完成以後被調用,即在包含的函數體中指定的地方「回頭調用」。小程序
也就是:A(主函數)讓 B(參數)作事,B作着作着,信息不夠,不知道怎麼作了,就須要A告訴他,這時,A到外面獲取信息,待A執行完畢後拿到了所需信息,再回過頭來調用B。segmentfault
網上也有一個通俗易懂的例子幫助理解回調函數:
你到一個商店買東西,恰好你要的東西沒有貨,因而你在店員那裏留下了你的電話,過了幾天店裏有貨了,店員就打了你的電話,而後你接到電話後就到店裏去取了貨。在這個例子裏,你的電話號碼就叫回調函數,你把電話留給店員就叫登記回調函數,店裏後來有貨了叫作觸發了回調關聯的事件,店員給你打電話叫作調用回調函數,你到店裏去取貨叫作響應回調事件。
回調函數的使用場景:主要是須要將父函數的執行結果通知給回調函數進行處理時使用。
//回調函數舉例1
$("#btn").click(() => {
alert("我是回調函數裏的內容,點擊後纔出現,並無當即執行");
});
//回調函數舉例2
function runAsyncMain(callback){
callback();
console.log('我是主函數');
}
function runAsyncCallback(){
setTimeout(() => {
console.log('我是回調函數');
}, 2000); //此處模擬延遲加載
}
runAsyncMain(runAsyncCallback); //(先輸出)我是主函數 (2s後輸出) 我是回調函數
複製代碼
可見此處主函數runAsyncMain執行的過程當中,按順序本應先執行回調函數,但輸出結果倒是後輸出的回調函數內容,這說明,主函數不用等回調函數執行完再執行後續的語句,能夠接着執行本身的代碼,等回調函數準備好,再執行回調函數。所謂的異步加載也不過如此,固然,異步與回調並無直接的聯繫,回調只是異步的一種實現方式。
介紹完回調函數,要回到Promise的主場了。程序執行過程當中,有很是多的應用場景咱們不能當即知道應該如何繼續往下執行,例如很重要的ajax請求
。通俗來講,因爲網速的不一樣,可能你獲得返回值的時間也不一樣,這時咱們就須要等某個結果出來了以後才知道怎麼樣繼續下去,例以下方的回調函數案例:
// 需求:當一個ajax結束後,獲得的值,須要做爲另一個ajax的參數被使用(即該參數得從上一個ajax請求中獲取)
var url = 'XXXXXX';
var result;
var XHR = new XMLHttpRequest();
XHR.open('GET', url, true); //第一個ajax請求
XHR.send();
XHR.onreadystatechange = function() {
if (XHR.readyState == 4 && XHR.status == 200) {
result = XHR.response;
console.log(result);
// 僞代碼
var url2 = 'XXXXXX' + result.someParams; //經過第一個ajax請求的結果,獲得第二個ajax請求所須要的url
var XHR2 = new XMLHttpRequest();
XHR2.open('GET', url2, true); //第二個ajax請求
XHR2.send();
XHR2.onreadystatechange = function() {
...
//往復上述過程,會獲得第三個ajax請求所須要的url值,而後進行第三個ajax請求,在獲得第四個…… 超恐怖
}
}
}
複製代碼
當上述需求中出現第三個ajax(甚至更多)仍然依賴上一個請求的時候,代碼就會變成一場災難。也就是咱們常說的回調地獄
。
這時,咱們可能會但願:
讓代碼變得更具備可讀性和可維護性,減輕一層層套用數據和請求的現象;
將請求和數據處理明確的區分開。
這時Promise
就要閃亮登場了,Promise中強大的then方法,能夠解決剛剛出現的恐怖的回調地獄
問題,而且讓代碼更優雅。
別急,咱們先從文檔中最基礎的 API 入手。
Promise
自己也是一個構造函數
,須要經過這個構造函數建立一個新的Promise
對象做爲接口,使用new
來調用Promise
的構造器來進行實例化,因此這個實例化出來的新對象:具備constructor屬性,而且指針指向他的構造函數Promise。
var promise = new Promise((resolve, reject) => {
// 此處代碼會當即執行
// 當調用棧內容處理結束後,再經過promise.then()方法調用resolve 或 reject返回的數據
});
複製代碼
Promise對象中的promise.then(resolve,reject)
實例方法,能夠接收構造函數中處理的狀態變化,經過此方法,設置了其值在resolve(成功)/reject(失敗)時調用的回調函數,並分別對應執行。
promise.then(onFulfilled, onRejected)
複製代碼
then方法有2個參數(都是可選參數,此參數是個回調函數):
resolve成功時onFulfilled
會被調用;
reject失敗時onRejected
會被調用。
promise.then
成功和失敗時均可以使用,而且then方法的執行結果也會返回一個Promise對象
。
另外在只想對異常進行處理時能夠採用promise.then(undefined, onRejected)
這種方式,只指定reject時的回調函數便可。不過這種狀況下 promise.catch(onRejected)
應該是個更好的選擇。
promise.catch(onRejected)
複製代碼
注意:在IE8及如下版本,使用promise.catch()
的代碼,會出現identifier not found的語法錯誤。(由於catch
與ECMAScript的保留字[2](Reserved Word)有關,在ECMAScript 3中保留字是不能做爲對象的屬性名使用的。)
解決辦法:不單純的使用catch
,而是使用then
來避免這個問題。
//then和catch方法 舉例
function asyncFunction(value) {
var p = new Promise((resolve, reject) => {
if(typeof(value) == 'number'){
resolve("我是數字");
}else {
reject("我不是數字");
}
});
return p;
}
// 寫法1:同時使用then和catch方法
asyncFunction(123).then(value => {
console.log(value);
}).catch(error => {
console.log(error);
});
//執行結果:我是數字
//解說:執行過程
// 一、在asyncFunction這個方法中建立一個Promise構造函數p,只要執行asyncFunction()就會返回一個Promise對象;
// 二、asyncFunction方法表示:若是傳入的參數value值是數字,就輸出"我是數字",若是不是數字,就輸出"我不是數字"
// 三、寫法1中,爲promise對象用設置 .then 方法調用返回值時的回調函數,.catch 方法來設置發生錯誤時的回調函數
// 四、promise對象會先判斷resolve,並執行then方法,若是resolve執行經過,則不會執行catch方法,若上面代碼在執行中產生異常,在 catch 中設置的回調函數就會被執行,並輸出error。
// 寫法2:只使用 then方法,不使用catch 方法
asyncFunction('abc').then((value) => {
console.log(value);
},(error) => {
console.log(error);
});
//執行結果:我不是數字
複製代碼
像Promise
這樣的全局對象還擁有一些靜態方法,後文中會有詳細解釋。
Promise.resolve()方法返回一個已給定值解析後的新的Promise對象,從而能繼續使用then的鏈式方法調用。
Promise.reject()方法和Promise.resolve()方法同樣。
Promise.all()方法的做用是將多個Promise
對象實例包裝,生成並返回一個新的Promise
實例。
Promise.race()與Promise.all()相反。
Promise的精髓是「狀態」,用維護狀態、傳遞狀態的方式來使得回調函數可以及時調用。
用new Promise
實例化的promise對象有如下三個狀態:
"unresolved" - Pending
| 既不是resolve也不是reject的狀態。等待中,或者進行中,表示Promise剛建立,尚未獲得結果時的狀態;
"has-resolution" - Fulfilled
| resolve(成功)時。此時會調用onFulfilled
;
"has-rejection" - Rejected
| reject(失敗)時。此時會調用onRejected
。
關於下面這三種狀態的讀法,其中左側爲在ES6 Promises規範中定義的術語,而右側則是在Promises/A+中描述狀態的術語。
上圖的意思是promise對象的狀態,從_Pending_轉換爲_Fulfilled_或_Rejected_以後, 這個promise對象的狀態就不會再發生任何變化,會一直保持這個結果。
當promise的對象狀態發生變化時,用.then
來定義只會被調用一次的函數。
前面不少次強調,Promise自己就是一個構造函數,因此能夠經過new建立新的Promise對象:
var p = new Promise((resolve, reject) => {
//作一些異步操做
setTimeout(() => {
console.log('執行完成');
resolve('個人數據');
}, 0);
console.log("我先執行")
});
//先輸出:我先執行
//1秒以後輸出:執行完成
複製代碼
咱們執行了一個異步操做
,也就是setTimeout,1秒後,輸出「執行完成」,而且調用resolve方法。可是隻是new了一個Promise對象,並無調用它,咱們傳進去的函數就已經執行了。爲了不這個現象產生,因此咱們用Promise的時候通常是包在一個函數中,須要的時候去運行這個函數。
若是你對執行的前後順序還不理解,推薦閱讀文章事件的循環機制(Event loop)[3]前文中咱們也曾屢次提到異步加載,因此此概念應熟記於心。
異步任務
:指不進入主線程、而進入"任務隊列"(task queue)的任務
,只有等主線程任務執行完畢,「任務隊列」開始通知主線程,請求執行任務,該任務纔會進入主線程執行。異步加載例如:你燒壺水要衝咖啡,但是水要10分鐘才能燒開,此時,你轉身去寫了個小程序,等10分鐘後水好了,纔回來繼續衝咖啡的活動,中間你去作了不少別的有意義的事情,也並無耽誤衝咖啡這項任務,這就是異步。
也能夠理解爲能夠改變程序正常執行順序的操做就能夠當作是異步操做。例如setTimeout和setInterval函數。
同步任務
:指在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;同步加載例如:你燒壺水要衝咖啡,但是水要10分鐘才能燒開,此時,你就等啊等啊等,等了10分鐘水好了,才繼續衝咖啡的活動,中間的過程就是等待,啥都不幹,這就是同步。
推薦參考文章:完全理解同步、異步和事件循環(Event Loop)[4]
你會發現,異步加載的舉例和上文中強調的回調函數例子很像,但剛剛也強調了,異步與回調並無直接的聯繫,回調只是異步的一種實現方式(再次重複,加深理解)。
你會不會覺得異步像是多線程操做那樣並列執行程序?我想說,千萬不要這樣想!js就是單線程,沒有多線程一說,因此不存在並行,即使是異步,也是單線程,只不過是放在了異步隊列裏,對,是隊列,假若你不是很理解,那麼請前去了解一下事件循環機制中的****宏任務和微任務(promise就是微任務,settimeout是宏任務,很是不錯的一篇文章)的區別,它們所在的隊列是不一樣的,看過以後,相信你會對promise有更深入的瞭解。
function asyncFunction(num) {
var p = new Promise((resolve, reject) => { //建立一個Promise的新對象p
if (typeof num == 'number') {
resolve();
} else {
reject();
}
});
p.then(function() { //這個(第一個)function是resolve對應的參數
console.log('這個是數字');
}, function() { //這個(第二個)function是reject對應的參數
console.log('我不是數字');
})
return p; //此處返回對象p
}
//執行這個函數咱們獲得了一個Promise構造出來的對象p,因此p.__proto__ === Promise.prototype,即p的指針指向了構造函數Promise,所以asyncFunction()可以使用Promise的屬性和方法
//此種寫法能夠屢次調用asyncFunction這個方法
asyncFunction('hahha'); //我不是數字
asyncFunction(1234); //這個是數字
複製代碼
咱們剛剛講到,then方法的執行結果也會返回一個Promise對象
,獲得一個結果。所以咱們能夠進行then的鏈式執行,接收上一個then返回回來的數據並繼續執行,這也是解決回調地獄
的主要方式。
下面咱們就來看看.then和.catch這兩個方法返回的究竟是不是新的promise對象。
var aPromise = new Promise(resolve => {
resolve(100);
});// aPromise爲原promise對象
// 下面分開進行.then和.catch操做
var thenPromise = aPromise.then(value => {
console.log(value);
});
var catchPromise = thenPromise.catch(error => {
console.error(error);
});
console.log(aPromise !== thenPromise); // => true
console.log(aPromise !== catchPromise); // => true
console.log(thenPromise !== catchPromise);// => true
複製代碼
===
是嚴格相等比較運算符,咱們能夠看出這三個對象都是互不相同的,這也就證實了then
和catch
都返回了和調用不一樣的promise對象。咱們經過下面這個例子進一步來理解:
// 1: 對同一個promise對象同時調用 `then` 方法
var aPromise = new Promise(resolve => {
resolve(100);
});
aPromise.then(value => {
return value * 2;
});
aPromise.then(value => {
return value * 2;
});
aPromise.then(value => {
console.log("1: " + value); // 1: 100
})
// vs
// 2: 對 `then` 進行 promise 鏈式 方式進行調用
var bPromise = new Promise(resolve => {
resolve(100);
});
bPromise.then(value => {
return value * 2;
}).then(value => {
return value * 2;
}).then(value => {
console.log("2: " + value); // 2: 400
});
複製代碼
寫法1 中並無使用promise的方法鏈方式,這在Promise中應該是極力避免的寫法。這種寫法中的then
調用幾乎是同時開始執行的,並且傳給每一個then
方法的value
值都是100
。
寫法2 則採用了方法鏈的方式將多個then
方法調用串連在了一塊兒,各函數也嚴格按照 resolve → then → then → then 的順序執行,而且傳給每一個then
方法的value
的值都是前一個promise對象經過return
返回的值,實現了Promise的數據傳遞。
強調:promise的鏈式操做實現了數據的傳遞,promise非鏈式操做的方法沒法實現數據傳遞。
剛剛在開篇(【爲何須要Promise】 這一節),經過一個ajax的例子,引出了回調地獄的概念,強調了經過回調函數方式解決多級請求都依賴於上一級數據時所引起的問題。下面咱們經過剛剛學習過的Promise內容(特別是.then的鏈式數據傳遞)對上面的ajax數據依賴的案例進行重寫:
var url = 'XXXXX';
// 封裝一個get請求的方法
function getJSON(url) {
return new Promise((resolve, reject) => {
var XHR = new XMLHttpRequest();
XHR.open('GET', url, true);
XHR.send();
XHR.onreadystatechange = function() {
if (XHR.readyState == 4) {
if (XHR.status == 200) {
try {
var response = JSON.parse(XHR.responseText);
resolve(response);
} catch (e) {
reject(e);
}
} else {
reject(new Error(XHR.statusText));
}
}
}
})
}
getJSON(url)
.then(resp => {
console.log(response);
return url2 = 'http:xxx.yyy.com/zzz?ddd=' + resp;
})
.then(resp => {
console.log(response);
return url3 = 'http:xxx.yyy.com/zzz?ddd=' + resp;
});
複製代碼
一、Promise.resolve
new Promise(resolve => {
resolve(100);
});
// 等價於
Promise.resolve(100); //Promise.resolve(100); 能夠認爲是上述代碼的語法糖。
// 使用方法
Promise.resolve(100).then(value => {
console.log(value);
});
複製代碼
另:Promise.resolve
方法另外一個做用就是將thenable對象轉換爲promise對象。
ES6 Promises提到了Thenable這個概念,簡單來講它就是一個很是相似promise的東西。就像咱們有時稱具備.length
方法的非數組對象爲類數組(Array like)同樣,thenable指的是一個具備.then
方法的對象。
由於:類庫沒有提供Promise
的實現,用戶經過Promise.resolve(thenable)
來本身實現了Promise
,而且,做爲Promise使用的時候,須要和Promise.resolve(thenable)
一塊兒配合使用,將thenable對象轉換promise對象:
var promise = Promise.resolve($.ajax('/json/comment.json'));// => promise對象
promise.then(function(value){
console.log(value);
});
複製代碼
在此再也不對Thenable進行過多贅述,可自行了解。
二、Promise.reject
new Promise((resolve,reject) => {
reject(new Error("出錯了"));
});
// 等價於
Promise.reject(new Error("出錯了")); // Promise.reject(new Error("出錯了")) 就是上述代碼的語法糖。
// 使用方法
Promise.reject(new Error("BOOM!")).catch(error => {
console.error(error);
});
複製代碼
Promise.all
接收一個promise對象的數組做爲參數
,當這個數組裏的全部promise對象所有變爲resolve或reject狀態的時候,它纔會去調用.then
方法。也就是說:Promise的all方法提供了異步操做的能力,而且在全部異步操做執行完後才執行回調。
// `delay`毫秒後執行resolve
function timerPromisefy(delay) {
return new Promise(resolve => {
setTimeout(() => {
resolve(delay);
}, delay);
});
}
var startDate = Date.now();
// 全部promise變爲resolve後程序退出
Promise.all([
timerPromisefy(1),
timerPromisefy(32),
timerPromisefy(64),
timerPromisefy(128)
]).then(values => {
console.log(Date.now() - startDate + 'ms');
// 約128ms
console.log(values); // [1,32,64,128]
});
複製代碼
這說明timerPromisefy
會每隔一、3二、6四、128ms都會有一個promise發生resolve
行爲,返回一個promise對象,狀態爲FulFilled,其狀態值爲傳給timerPromisefy
的參數,而且all會把全部異步操做的結果放進一個數組中傳給then。
從上述結果能夠看出,傳遞給Promise.all
的promise並非一個個的順序執行的,而是同時開始、並行執行
的。
all方法的效果其實是「誰跑的慢,以誰爲準執行回調」,那麼相對的就有另外一個方法「誰跑的快,以誰爲準執行回調」,這就是race方法,這個詞原本就是賽跑的意思。race的用法與all同樣,接收一個promise對象數組爲參數。
Promise.all
在接收到的全部的對象promise都變爲FulFilled或者Rejected狀態以後纔會繼續進行後面的處理,與之相對的是Promise.race
只要有一個promise對象進入FulFilled或者Rejected狀態的話,就會繼續進行後面的處理。
// `delay`毫秒後執行resolve
function timerPromisefy(delay) {
return new Promise(resolve => {
setTimeout(() => {
resolve(delay);
}, delay);
});
}
// 任何一個promise變爲resolve或reject 的話程序就中止運行
Promise.race([
timerPromisefy(1),
timerPromisefy(32),
timerPromisefy(64),
timerPromisefy(128)
]).then(function (value) {
console.log(value); // => 1
});
複製代碼
上面的代碼建立了4個promise對象,這些promise對象會分別在1ms、32ms、64ms和128ms後變爲肯定狀態,即FulFilled,而且在第一個變爲肯定狀態的1ms後,.then
註冊的回調函數就會被調用,這時候肯定狀態的promise對象會調用resolve(1)
所以傳遞給value
的值也是1,控制檯上會打印出1
來。
promise的基本使用原理以及它在實際應用中爲咱們解決的問題,在上述過程當中已經介紹完了,你是否理解了呢?學習是一個反覆閱讀,反覆加深印象的過程,加油緊緊掌握這一知識點,在vue、react等框架的使用中,也會頻繁用到有關promise的知識,下面一塊兒來檢測一下對promise的認知結果吧。
下面內容的輸出結果應該是啥?
function test1() {
console.log("test1");
}
function test2() {
console.log("test2");
}
function onRejected(error) {
console.log("捕獲錯誤: test1 or test2", error);
}
function test3() {
console.log("end");
}
var promise = Promise.resolve();
promise
.then(test1)
.then(test2)
.catch(onRejected)
.then(test3);
複製代碼
舒適提示:這裏沒有爲then
方法指定第二個參數(onRejected)。
補充1:對比 callback → Promise → async/await
javascript的異步發展歷程,從callback,到Promise對象、Generator函數,不停地優化程序上的編寫方式,但又讓人以爲不是很完全,隨即又有了以後的async/await的異步編程方式,讓異步編程變得更像同步代碼,加強了代碼的可讀性,甚至不少人評價async/await是異步操做的終極解決方案,接下來簡單介紹一下這三種方式各自的優缺點:
1.callback(回調):本文開篇也說起了回調函數雖然好理解,但只對於簡單的異步程序,callback是能夠勝任的,可是在ajax須要被屢次調用時使用起來仍是會產生不少問題:
高耦合,讓程序變得難以維護;
而且錯誤捕捉要經過人工的設置判斷來進行。
2.Promise:ES6提供的構造函數Promise的實現是要基於callback的,解決了異步執行的問題:
經過Promise.then()鏈式調用的方法,解決了回調函數層層嵌套(回調地獄)的問題,讓代碼和操做都變得更加簡潔;
能夠統一經過Promise.catch()方法對異常進行捕獲,無需再像callback那樣,爲每一個異步操做添加異常處理;
Promise.all()方法能夠對異步操做進行並行處理,同時執行多個操做。
但Promise也存在缺點:
當處於未完成狀態時,沒法肯定目前處於哪一階段;
若是不設置回調函數,Promise內部的錯誤不會反映到外部;
Promise一旦新建它就會當即執行,沒法中途取消。
ES6中,還有一個generator函數,之前一個函數中的代碼要麼被調用,要麼不被調用,不存在能暫停的狀況,generator函數讓代碼能夠中途暫停、異步執行,它與Promise的結合使用,相似於async/await(見下文)效果的代碼。
整個Generator函數就是一個封裝的異步任務的容器。它的語法是在函數名前加個*號,在異步操做須要暫停的地方,都用yield語句註明,但僅有yield,函數是不會執行的,他須要調用next方法,指針都會向下移一個狀態,直到遇到下一個yield
表達式(或return
語句)爲止。
3**.async/await**:ES7中新增的異步編程方法,async/await的實現是基於 Promise的,簡單而言就是**async function就是返回Promise的function,是generator的語法糖,其實async函數就是將Generator函數的星號(*)替換成 async,將yield替換成await。**不少人認爲async/await是異步操做的終極解決方案:
改進JS中異步操做串行執行的代碼組織方式,減小callback的嵌套;
語法簡潔,更像是同步代碼,也更符合普通的閱讀習慣;
Promise中不能自定義使用try/catch進行錯誤捕獲,可是在Async/await中能夠像處理同步代碼處理錯誤。
綜上異步編程的演變過程可見,它語法目標,其實就是怎樣讓它更像同步編程。
若是你想要更詳細的瞭解Generator和async/await異步操做方式,請查閱MDN中的相關文檔。同時推薦一篇通俗易懂的文章:www.lazycoffee.com/articles/vi…
補充2:Promise 的實現原理
一塊兒回顧一下, 前面咱們介紹了promise的來由、解決的問題、經常使用和重點的方法、以及promise的使用方法和應用場景,你是否是也很好奇,沒有promise的話,咱們要如何模擬出promise呢?也就是,promise是如何實現的?
回顧一下Promise的使用過程:
**1.**首先要知道,Promise對象有三個狀態:Pending(進行中)
、Fulfilled(已成功)
、Rejected(已失敗)
,因此須要Promise設置三個狀態值;
**2.**Promise存在resolve和reject兩個回調函數做爲自身參數:new Promise((resolve, reject){});,經過判斷步驟1中的三個狀態值,來肯定輸出哪一個方法,例如:
promise處於Fulfilled狀態時,就輸出resolve對應的方法;
Rejected狀態時就輸出reject方法。
**3.**Promise的then方法要接收兩個參數:promise.then(onFulfilled, onRejected),onFulfilled和onRejected也必須是兩個函數:
當Promise的狀態爲成功時,調用onFulfilled這個方法,其中onFulfilled方法中的參數是步驟2中promise成功狀態 resolve執行時傳入的值;
當Promise的狀態爲失敗時,調用onRejected這個方法,其中onRejected方法中的參數是步驟2中promise失敗狀態reject執行時傳入的值。
**4.**若是then被同一個Promise屢次調用,全部onFulfilled
和onRejected
需按照其註冊順序依次回調;
5.……
**6.**固然,Promise還有.catch、.all、.race等不少方法,以及基本邏輯和規則肯定後,還須要加上錯誤捕獲、值傳遞等機制,例如判斷步驟2中的回調函數是否爲function、步驟4鏈式操做中,then返回的是否爲一個新的Promise對象等等;
具體的Promise實現原理代碼以下,如有興趣的童鞋可參考閱讀,幫助進一步加深理解:
// 判斷變量否爲function
const isFunction = variable => typeof variable === 'function'
// 定義Promise的三種狀態常量
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class MyPromise {
constructor (handle) {
if (!isFunction(handle)) {
throw new Error('MyPromise must accept a function as a parameter')
}
// 添加狀態
this._status = PENDING
// 添加狀態
this._value = undefined
// 添加成功回調函數隊列
this._fulfilledQueues = []
// 添加失敗回調函數隊列
this._rejectedQueues = []
// 執行handle
try {
handle(this._resolve.bind(this), this._reject.bind(this))
} catch (err) {
this._reject(err)
}
}
// 添加resovle時執行的函數
_resolve (val) {
const run = () => {
if (this._status !== PENDING) return
// 依次執行成功隊列中的函數,並清空隊列
const runFulfilled = (value) => {
let cb;
while (cb = this._fulfilledQueues.shift()) {
cb(value)
}
}
// 依次執行失敗隊列中的函數,並清空隊列
const runRejected = (error) => {
let cb;
while (cb = this._rejectedQueues.shift()) {
cb(error)
}
}
/* 若是resolve的參數爲Promise對象,則必須等待該Promise對象狀態改變後,
當前Promsie的狀態纔會改變,且狀態取決於參數Promsie對象的狀態
*/
if (val instanceof MyPromise) {
val.then(value => {
this._value = value
this._status = FULFILLED
runFulfilled(value)
}, err => {
this._value = err
this._status = REJECTED
runRejected(err)
})
} else {
this._value = val
this._status = FULFILLED
runFulfilled(val)
}
}
// 爲了支持同步的Promise,這裏採用異步調用
setTimeout(run, 0)
}
// 添加reject時執行的函數
_reject (err) {
if (this._status !== PENDING) return
// 依次執行失敗隊列中的函數,並清空隊列
const run = () => {
this._status = REJECTED
this._value = err
let cb;
while (cb = this._rejectedQueues.shift()) {
cb(err)
}
}
// 爲了支持同步的Promise,這裏採用異步調用
setTimeout(run, 0)
}
// 添加then方法
then (onFulfilled, onRejected) {
const { _value, _status } = this
// 返回一個新的Promise對象
return new MyPromise((onFulfilledNext, onRejectedNext) => {
// 封裝一個成功時執行的函數
let fulfilled = value => {
try {
if (!isFunction(onFulfilled)) {
onFulfilledNext(value)
} else {
let res = onFulfilled(value);
if (res instanceof MyPromise) {
// 若是當前回調函數返回MyPromise對象,必須等待其狀態改變後在執行下一個回調
res.then(onFulfilledNext, onRejectedNext)
} else {
//不然會將返回結果直接做爲參數,傳入下一個then的回調函數,並當即執行下一個then的回調函數
onFulfilledNext(res)
}
}
} catch (err) {
// 若是函數執行出錯,新的Promise對象的狀態爲失敗
onRejectedNext(err)
}
}
// 封裝一個失敗時執行的函數
let rejected = error => {
try {
if (!isFunction(onRejected)) {
onRejectedNext(error)
} else {
let res = onRejected(error);
if (res instanceof MyPromise) {
// 若是當前回調函數返回MyPromise對象,必須等待其狀態改變後在執行下一個回調
res.then(onFulfilledNext, onRejectedNext)
} else {
//不然會將返回結果直接做爲參數,傳入下一個then的回調函數,並當即執行下一個then的回調函數
onFulfilledNext(res)
}
}
} catch (err) {
// 若是函數執行出錯,新的Promise對象的狀態爲失敗
onRejectedNext(err)
}
}
switch (_status) {
// 當狀態爲pending時,將then方法回調函數加入執行隊列等待執行
case PENDING:
this._fulfilledQueues.push(fulfilled)
this._rejectedQueues.push(rejected)
break
// 當狀態已經改變時,當即執行對應的回調函數
case FULFILLED:
fulfilled(_value)
break
case REJECTED:
rejected(_value)
break
}
})
}
// 添加catch方法
catch (onRejected) {
return this.then(undefined, onRejected)
}
// 添加靜態resolve方法
static resolve (value) {
// 若是參數是MyPromise實例,直接返回這個實例
if (value instanceof MyPromise) return value
return new MyPromise(resolve => resolve(value))
}
// 添加靜態reject方法
static reject (value) {
return new MyPromise((resolve ,reject) => reject(value))
}
// 添加靜態all方法
static all (list) {
return new MyPromise((resolve, reject) => {
/**
* 返回值的集合
*/
let values = []
let count = 0
for (let [i, p] of list.entries()) {
// 數組參數若是不是MyPromise實例,先調用MyPromise.resolve
this.resolve(p).then(res => {
values[i] = res
count++
// 全部狀態都變成fulfilled時返回的MyPromise狀態就變成fulfilled
if (count === list.length) resolve(values)
}, err => {
// 有一個被rejected時返回的MyPromise狀態就變成rejected
reject(err)
})
}
})
}
// 添加靜態race方法
static race (list) {
return new MyPromise((resolve, reject) => {
for (let p of list) {
// 只要有一個實例率先改變狀態,新的MyPromise的狀態就跟着改變
this.resolve(p).then(res => {
resolve(res)
}, err => {
reject(err)
})
}
})
}
finally (cb) {
return this.then(
value => MyPromise.resolve(cb()).then(() => value),
reason => MyPromise.resolve(cb()).then(() => { throw reason })
);
}
}
// 源碼連接:https://juejin.im/post/5b83cb5ae51d4538cc3ec354
複製代碼
[1].阮一峯ECMAScript 6入門(es6.ruanyifeng.com/#docs/funct…)
[2].保留字(mothereff.in/js-properti…)
[3].事件的循環機制(Event loop)(www.jianshu.com/p/12b9f73c5…)~~
[4].完全理解同步、異步和事件循環(Event Loop)(segmentfault.com/a/119000000…) (developer.mozilla.org/zh-CN/docs/…)
[5].JavaScript Promise迷你書(liubin.org/promises-bo…)
[6].透徹掌握Promise的使用(www.jianshu.com/p/fe5f17327…)