Promise
規範有不少,如Promise/A,Promise/B,Promise/D
以及Promise/A
的升級版Promise/A+
。ES6 中採用了Promise/A+
規範。 什麼是 Promise/A+ 規範,推薦一篇文章Promises/A+規範(中文);jquery
1.一個promise
的當前狀態只能是pending
、fulfilled
和rejected
三種之一。狀態改變只能是pending
到fulfilled
或者pending
到rejected
,而且狀態改變不可逆的。 2.promise
的then
方法接收兩個可選參數,表示該promise
狀態改變時的回調(promise.then(onFulfilled, onRejected
))。then
方法返回一個promise
。then
方法能夠被同一個promise
調用屢次。數組
const promise = new Promise((resolve) => {
setTimeout(()=> {
resolve(1);
}, 2000);
});
promise.then(a=> alert(a));
複製代碼
上面這段代碼的解讀:promise
executor
當即執行函數;executor
當即執行函數接收一個resolve
函數;promise
對象的then
方法綁定狀態變爲fulfilled
時的回調,then
方法也能夠被稱爲註冊函數(這句話怎麼理解呢?不着急,下面會解釋);resolve
函數被調用時會觸發then方法中註冊的回調函數(這句話很重要,在我看來,能理解這句話,就能理解promise
);用你們都會用,那麼,咱們來實現一個最最最簡單的promisebash
直接看下面這段代碼異步
function MyPromise(executor){
var self = this;
this.status = "pending";
this.data = "";
this.resolveArr = [];
this.rejectArr = [];
function resolve(data){
//問題:爲啥要在這裏作異步處理(如過如今不能理解,等看完必定能理解的)
self.data = data
setTimeout(function(){
self.resolveArr.forEach((fn)=>{
fn(data);
});
},0);
}
function reject(){}
try{
executor(resolve,reject)
}catch(err){
reject(err)
}
}
MyPromise.prototype.then = function(resolveFn,rejectFn){
this.resolveArr.push(resolveFn);
this.rejectArr.push(rejectFn);
}
複製代碼
來解釋一下上邊這段代碼函數
MyPromise
接收了一個executor
做爲參數,在實例化的時候去執行;這個executor
接收兩個參數resolve
和reject
(註釋:和原生的Promise(function(resolve,reject){}
)是保持一致的);測試
接下來看resolve
和reject
這倆參數,在MyPromise
函數體內,能夠看到,這兩個參數實際上是定義好的,能夠理解爲一個成功回調和一個失敗的回調,和原生的Promise
保持一致(在接下來的講解中,基本以成功回調的resolve
來作說明);來看下resolve
函數內部作了什麼?發現resolve
只作了一件事,遍歷了函數體內定義的resolveArr
,而後執行了resolveArr
了每一項,並把data
參數傳遞進去;優化
那麼問題來了,既然resolveArr的每一項均可以執行,那就是函數咯,咱們能夠看到,在函數體內,咱們只定義了一個空的resolveArr,那這個函數是怎麼來的呢?ui
then
方法裏面有用到這個數組(在文章的前面有說到,then
方法能夠理解爲註冊方法,怎麼個註冊法呢?),原來在這個then
方法會接收兩個函數類型的參數(和原生的Promise
保持一致);並把第一個參數添加到resolveArr
中,第二個參數添加到rejectArr
中。這也就完成了註冊!說了那麼多,來測試一下,是否有效:this
var test = new MyPromise(function(resolve){
setTimeout(()=>{
resolve("測試");
},1000);
});
test.then((data)=>{
console.log(data);
});
複製代碼
運行結果以下:
能夠看到1秒鐘之後會打印出結果(因爲技術緣由,不會作gif圖,見諒);重點再次提醒:時刻記住一句話,resolve
函數被調用時會觸發then方法中註冊的回調函數,從上面這個案例能夠充分的體現;
理解了上面這個案例之後,咱們來慢慢的作優化
上面提到,then方法返回一個promise。then 方法能夠被同一個 promise 調用屢次。來改寫下then方法:
MyPromise.prototype.then = function(onResolved,onRejected){
this.resolveArr.push(onResolved);
this.rejectArr.push(onRejected);
return this;
}
複製代碼
只須要在then
的最後return this
便可,看過jquery
源碼的同窗應該都很熟悉吧! 問題點:這樣作的話,全部then
方法註冊的函數所接收的值都是同樣的,這顯然是咱們不能接收的,後面還會優化
上面提到,一個promise
的當前狀態只能是pending
、fulfilled
和rejected
三種之一。狀態改變只能是pending
到fulfilled
或者pending
到rejected
。來改下函數體內的resolve
方法:
function resolve(data){
if(self.status==="pending"){
self.status = "fulfilled";
setTimeout(function(){
self.resolveArr.forEach((fn)=>{
fn(data);
});
},0)
}
}
複製代碼
只需在遍歷resolveArr
前判斷狀態是否爲pending
,若是是,則改變狀態爲fulfilled
,而後執行遍歷,這裏也解釋了文章前面提到的一句話(promise
對象的then
方法綁定狀態變爲fulfilled
時的回調);
針對優化一提出的問題,來作進一步的優化
文章開頭對promise/A+規範的標準解讀中提到(then
方法返回一個promise
),那咱們就給then
方法返回一個promise,來改寫then
方法:
MyPromise.prototype.then = function(onResolved,onRejected){
var self = this;
return new MyPromise(function(resolve,reject){
self.resolveArr.push(function(){
var x = onResolved(self.data);
if(x instanceof MyPromise){
//當then返回的是一個MyPromise的時候,會把resove掛在到該MyPromise的resolveArr隊列中,等待該MyPromise執行對應的resolve;
x.then(resolve,reject)
}else{
//當then的返回值不是一個MyPromise的時候,直接執行resolve跳到下一個then處理;
resolve(x);
}
});
});
}
複製代碼
再次強調:resolve
函數被調用時會觸發then方法中註冊的回調函數
提問:這裏涉及了幾個MyPromise
實例?
答案:2個或者3個;
爲何呢?
來看一下,代碼中有一句var x = onResolved(self.data)
,並在後面判斷了x
是否是屬於MyPromise
類型;從代碼中也能夠看出,x
是函數onResolved
的返回結果,那這個onResolved
又是什麼呢?原來就是調用then
方法時所註冊的函數;當x
也是一個MyPromise
實例的時候,這裏就涉及了三個MyPromise
實例,分別是(當前實例(this
),then
方法返回的實例,註冊函數返回的實例(x
));
分析
爲了方便,我把當前實例稱爲A
,then
方法返回的實例稱爲B
; 當A
調用then
方法的時候,直接返回了B
,並在B
初始化的時候,給A
的resolveArr
加入一個匿名函數(這裏記爲AResolveFn
),當A
中的resolve
執行的時候,會去執行這個AResolveFn
,在這個AResolveFn
中,會去執行咱們傳入then
中的onResolved
方法,並把返回結果記x
。
狀況一:當x不爲MyPromise
從代碼中能夠看出,在這種狀況下,直接執行了resolve(x)
;這個resolve
是B
的resolve
,那B
的resolve
執行了,就會觸發B
實例用then
方法註冊的方法,這樣就實現了then
的鏈式調用,而且把每次註冊方法的返回值傳下去啦!
測試:
var test = new MyPromise(function(resolve){
setTimeout(()=>{
resolve("測試");
},1000);
});
test.then((data)=>{
console.log(data);
return "測試2"
}).then((data)=>{
console.log(data);
});
複製代碼
測試結果:
打印結果沒問題。狀況二:當x爲MyPromise
從代碼上看,當x
爲MyPromise
的時候,直接把B
的resolve
當作了x
的註冊方法;這樣的話,只有當x
的resolve
執行的時候,會觸發x
用then
註冊的方法,纔會觸發B
的resolve
,纔會觸發B
用then
註冊的方法;
流程就是:A
的resolve
--> A
用then
註冊的方法 --> x
的resolve
--> x
用then
註冊的方法 --> B
的resolve
--> B
用then
註冊的方法,這樣就實現了異步鏈式傳遞;
測試:
var test = new MyPromise(function(resolve){
setTimeout(()=>{
resolve("測試");
},1000);
});
test.then((data)=>{
console.log(data);
return new MyPromise(function(resolve){
setTimeout(()=>{
resolve("測試2");
},1000)
})
}).then((data)=>{
console.log(data);
});
複製代碼
測試結果:
因爲沒有作gif圖,看不出效果,可是從圖中的執行時間能夠看出來,整個過程確實是執行了2秒多;以上說到的都是正常的狀態下;就是說,當你用then方法註冊函數的時候,都是在pending狀態;可是,有的特殊場景,在你用then
方法的時候,狀態已是fulfilled
,就像下面這樣:
var test = new MyPromise(function(resolve,reject){
setTimeout(function(){
resolve("test")
},1000);
});
setTimeout(function(){
test.then(function(data){
console.log(data);
});
},2000)
複製代碼
在這種狀況下,當調用resolve
方法的時候,並無用then方法註冊函數,那麼resolveArr
必然是一個空數組;此時狀態是fulfilled
,不可能說你用then
方法註冊的時候,再給你執行一邊,也違背了Promise
的標準。
MyPromise.prototype.then = function(onResolved,onRejected){
var self = this;
if(self.status==="fulfilled"){
return new MyPromise(function(resolve,reject){
var x = onResolved(self.data);
if(x instanceof MyPromise){
x.then(resolve,reject)
}else{
resolve(x);
}
});
}
if(self.status==="pending"){
return new MyPromise(function(resolve,reject){
self.resolveArr.push(function(){
var x = onResolved(self.data);
if(x instanceof MyPromise){
x.then(resolve,reject)
}else{
resolve(x);
}
});
});
}
}
複製代碼
在then
方法中作了一層判斷,當狀態是fulfilled
的時候,不會執行resolve
,固然也不會觸發then
註冊的函數,這裏的作法是跳過這一層,直接去執行onResolved
,而後繼續走下去;上面原理都理解的話,這裏理解起來應該也不是特別困難,這裏就不作過多解釋了,實在理不清楚的話就留言給我吧!
原理到這裏基本上就差很少了,這裏再補充一個promise靜態方法
Promise
中有許多靜態方法,像Promise.all
,Promise.race
,那咱們用以上本身實現的MyPromise
來加一個靜態方法試試:
MyPromise.all = function(promiseArr){
return new MyPromise(function(resolve){
var length = promiseArr.length;
var resultIndex = 0;
var resultArr = [];
promiseArr.forEach((promise)=>{
promise.then(function(data){
resultArr.push(data);
resultIndex++;
if(resultIndex===length){
resolve(resultArr);
}
});
});
});
}
複製代碼
原理理解的話,這個實現起來也就不難了,這裏就不講解了,直接來測試一下吧:
var test1 = new MyPromise(function(resolve){
setTimeout(function(){
resolve("test1")
},1000)
});
var test2 = new MyPromise(function(resolve){
setTimeout(function(){
resolve("test2")
},1500)
});
var test3 = new MyPromise(function(resolve){
setTimeout(function(){
resolve("test3")
},1200)
});
複製代碼
結果以下:
結果沒有問題吧,返回了一個數組,很開心有不有!有興趣的小夥伴能夠跟着這個思路,去實現一下其餘靜態方法。關於promise
的內容到這裏就差很少了,平時也比較少發表文章,總怕本身總結的很差,但願能給你們帶來幫助吧!