轉自: http://www.jianshu.com/p/473cd754311f
Promise
看了些promise的介紹,還是感覺不夠深入,這個在解決異步問題上是一個很好的解決方案,所以詳細看一下,順便按照自己的思路實現一個簡單的Promise。
Promise/A+規范:
- 首先重新閱讀了下A+的規范:
- promise代表了一個異步操作的最終結果,主要是通過then方法來注冊成功以及失敗的情況,
- Promise/A+歷史上說是實現了Promise/A的行為並且考慮了一些不足之處,他並不關心如何創建,完成,拒絕Promise,只考慮提供一個可協作的then方法。
術語:
promise是一個擁有符合上面的特征的then方法的對象或者方法。thenable是定義了then方法的對象或者方法value是任何合法的js的值(包括undefined,thenable或者promise)exception是一個被throw申明拋出的值reason是一個指明了為什么promise被拒絕
狀態要求:
- promise必須是在pending,fulfilled或者rejected之間的一種狀態。
- promise一旦從pending變成了fulfilled或則rejected,就不能再改變了。
- promise變成fulfilled之后,必須有一個value,並且不能被改變
- promise變成rejected之后,必須有一個reason,並且不能被改變
then方法的要求:
- promise必須有個then方法來接觸當前的或者最后的value或者reason
- then方法接受兩個參數,onFulfilled和onRejected,這兩個都是可選的,如果傳入的不是function的話,就會被忽略
- 如果onFulfilled是一個函數,他必須在promise完成后被執行(不能提前),並且value是第一個參數,並且不能被執行超過一次
- 如果onRejected是一個函數,他必須在promise拒絕后被執行(不能提前),並且reason是第一個參數,並且不能被執行超過一次
- onFulfilled或者onRejected只能在執行上下文堆只包含了平台代碼的時候執行(就是要求onfulfilled和 onrejected必須異步執行,必須在then方法被調用的那一輪事件循環之后的新執行棧執行,這里可以使用macro-task或者micro- task,這兩個的區別參見文章)
- onFulfilled或者onRejected必須作為function被執行(就是說沒有一個特殊的this,在嚴格模式中,this就是undefined,在粗糙的模式,就是global)
- then方法可能在同一個promise被調用多次,當promise被完成,所有的onFulfilled必須被順序執行,onRejected也一樣
-
then方法必須也返回一個promise(這個promise可以是原來的promise,實現必須申明什么情況下兩者可以相等)promise2 = promise1.then(onFulfilled, onRejected);
- 如果
onFulfilled和onRejected都返回一個value x,執行2.3Promise的解決步驟[Resolve] - 如果
onFulfilled和onRejected都拋出exception e,promise2必須被rejected同樣的e - 如果
onFulfilled不是個function,且promise1 is fulfilled,promise2也會fulfilled,和promise1的值一樣 - 如果
onRejected不是個function,且promise1 is rejected,promise2也會rejected,理由和promise1一樣
這里不論promise1被完成還是被拒絕,promise2 都會被 resolve的,只有出現了一些異常才會被rejected
- 如果
Promise的解決步驟==[Resolve]
- 這個是將
promise和一個值x作為輸入的一個抽象操作。如果這個x是支持then的,他會嘗試讓promise接受x的狀態;否則,他會用x的值來fullfill這個promise。運行這樣一個東西,遵循以下的步驟- 如果promise和x指向同一個對象,則reject這個promise使用TypeError。
- 如果x是一個promise,接受他的狀態
- 如果x在pending,promise必須等待x的狀態改變
- 如果x被fullfill,那么fullfill這個promise使用同一個value
- 如果x被reject,那么reject這個promise使用同一個理由
- 如果x是一個對象或者是個方法
- 如果x.then返回了錯誤,則reject這個promise使用錯誤。
- 如果then是一個方法,使用x為this,resolvePromise為一參,rejectPromise為二參,
- 如果resolvePromise被一個值y調用,那么運行[Resolve]
- 如果rejectPromise被reason r,使用r來reject這個promise
- 如果resolvePromise和rejectPromise都被調用了,那么第一個被調用的有優先權,其他的beihulue
- 如果調用then方法得到了exception,如果上面的方法被調用了,則忽略,否則reject這個promise
- 如果then方法不是function,那么fullfill這個promise使用x
- 如果x不是一個對象或者方法,那么fullfill這個promise使用x
如果promise產生了環形的嵌套,比如[Resolve]最終喚起了[Resolve],那么實現建議且並不強求來發現這種循環,並且reject這個promise使用一個TypeError。
接下來正式寫一個promise
思路都是最正常的思路,想要寫一個Promise,肯定得使用一個異步的函數,就拿setTimeout來做。
var p = new Promise(function(resolve){
setTimeout(resolve, 100);
});
p.then(function(){console.log('success')},function(){console.log('fail')});
初步構建
上面是個最簡單的使用場景我們需要慢慢來構建
function Promise(fn){
//需要一個成功時的回調
var doneCallback;
//一個實例的方法,用來注冊異步事件
this.then = function(done){
doneCallback = done;
}
function resolve(){
doneCallback();
}
fn(resolve);
}
加入鏈式支持
下面加入鏈式,成功回調的方法就得變成數組才能存儲
function Promise(fn){
//需要成功以及成功時的回調
var doneList = [];
//一個實例的方法,用來注冊異步事件
this.then = function(done ,fail){
doneList.push(done);
return this;
}
function resolve(){
doneList.forEach(function(fulfill){
fulfill();
});
}
fn(resolve);
}
這里promise里面如果是同步的函數的話,doneList里面還是空的,所以可以加個setTimeout來將這個放到js的最后執行。這里主要是參照了promiseA+的規范,就像這樣
function resolve(){
setTimeout(function(){
doneList.forEach(function(fulfill){
fulfill();
});
},0);
}
加入狀態機制
這時如果promise已經執行完了,我們再給promise注冊then方法就怎么都不會執行了,這個不符合預期,所以才會加入狀態這種東西。更新過的代碼如下
function Promise(fn){
//需要成功以及成功時的回調
var state = 'pending';
var doneList = [];
//一個實例的方法,用來注冊異步事件
this.then = function(done){
switch(state){
case "pending":
doneList.push(done);
return this;
break;
case 'fulfilled':
done();
return this;
break;
}
}
function resolve(){
state = "fulfilled";
setTimeout(function(){
doneList.forEach(function(fulfill){
fulfill();
});
},0);
}
fn(resolve);
}
加上異步結果的傳遞
現在的寫法根本沒有考慮異步返回的結果的傳遞,我們來加上結果的傳遞
function resolve(newValue){
state = "fulfilled";
var value = newValue;
setTimeout(function(){
doneList.forEach(function(fulfill){
value = fulfill(value);
});
},0);
}
支持串行
這樣子我們就可以將then每次的結果交給后面的then了。但是我們的promise現在還不支持promise的串行寫法。比如我們想要
var p = new Promise(function(resolve){
setTimeout(function(){
resolve(12);
}, 100);
});
var p2 = new Promise(function(resolve){
setTimeout(function(){
resolve(42);
}, 100);
});
p.then(
function(name){
console.log(name);return 33;
}
)
.then(function(id){console.log(id)})
.then(p2)
.then(function(home){console.log(home)});
所以我們必須改下then方法。
當then方法傳入一般的函數的時候,我們目前的做法是將它推進了一個數組,然后return this來進行鏈式的調用,並且期望在resolve方法調用時執行這個數組。
最開始我是研究的美團工程師的一篇博客,到這里的時候發現他的解決方案比較跳躍,於是我就按照普通的正常思路先嘗試了下:
如果傳入一個promise的話,我們先嘗試繼續推入數組中,在resolve的地方進行區分,發現是可行的,我先貼下示例代碼,然后會有詳細的注釋。
function Promise(fn){
//需要成功以及成功時的回調
var state = 'pending';
var doneList = [];
this.then = function(done){
switch(state){
case "pending":
doneList.push(done);
return this;
break;
case 'fulfilled':
done();
return this;
break;
}
}
function resolve(newValue){
state = "fulfilled";
setTimeout(function(){
var value = newValue;
//執行resolve時,我們會嘗試將doneList數組中的值都執行一遍
//當遇到正常的回調函數的時候,就執行回調函數
//當遇到一個新的promise的時候,就將原doneList數組里的回調函數推入新的promise的doneList,以達到循環的目的
for (var i = 0;i<doneList.length;i++){
var temp = doneList[i](value)
if(temp instanceof Promise){
var newP = temp;
for(i++;i<doneList.length;i++){
newP.then(doneList[i]);
}
}else{
value = temp;
}
}
},0);
}
fn(resolve);
}
var p = function (){
return new Promise(function(resolve){
setTimeout(function(){
resolve('p 的結果');
}, 100);
});
}
var p2 = function (input){
return new Promise(function(resolve){
setTimeout(function(){
console.log('p2拿到前面傳入的值:' + input)
resolve('p2的結果');
}, 100);
});
}
p()
.then(function(res){console.log('p的結果:' + res); return 'p then方法第一次返回'})
.then(function(res){console.log('p第一次then方法的返回:'+res); return 'p then方法第二次返回'})
.then(p2)
.then(function(res){console.log('p2的結果:' + res)});
加入reject
我按照正常思路這么寫的時候發現出了點問題,因為按照最上面的規范。即使一個promise被rejected,他注冊的then方法之后再注冊的 then方法會可能繼續執行resolve的。即我們在then方法中為了鏈式返回的this的status是可能會被改變的,假設我們在實現中來改變狀 態而不暴露出來(這其實一點也不推薦)。
我直接貼實現的代碼,還有注釋作為講解
function Promise(fn){
var state = 'pending';
var doneList = [];
var failList= [];
this.then = function(done ,fail){
switch(state){
case "pending":
doneList.push(done);
//每次如果沒有推入fail方法,我也會推入一個null來占位
failList.push(fail || null);
return this;
break;
case 'fulfilled':
done();
return this;
break;
case 'rejected':
fail();
return this;
break;
}
}
function resolve(newValue){
state = "fulfilled";
setTimeout(function(){
var value = newValue;
for (var i = 0;i<doneList.length;i++){
var temp = doneList[i](value);
if(temp instanceof Promise){
var newP = temp;
for(i++;i<doneList.length;i++){
newP.then(doneList[i],failList[i]);
}
}else{
value = temp;
}
}
},0);
}
function reject(newValue){
state = "rejected";
setTimeout(function(){
var value = newValue;
var tempRe = failList[0](value);
//如果reject里面傳入了一個promise,那么執行完此次的fail之后,將剩余的done和fail傳入新的promise中
if(tempRe instanceof Promise){
var newP = tempRe;
for(i=1;i<doneList.length;i++){
newP.then(doneList[i],failList[i]);
}
}else{
//如果不是promise,執行完當前的fail之后,繼續執行doneList
value = tempRe;
doneList.shift();
failList.shift();
resolve(value);
}
},0);
}
fn(resolve,reject);
}
var p = function (){
return new Promise(function(resolve,reject){
setTimeout(function(){
reject('p 的結果');
}, 100);
});
}
var p2 = function (input){
return new Promise(function(resolve){
setTimeout(function(){
console.log('p2拿到前面傳入的值:' + input)
resolve('p2的結果');
}, 100);
});
}
p()
.then(function(res){console.log('p的結果:' + res); return 'p then方法第一次返回'},function(value){console.log(value);return 'p then方法第一次錯誤的返回'})
.then(function(res){console.log('p第一次then方法的返回:'+res); return 'p then方法第二次返回'})
.then(p2)
.then(function(res){console.log('p2的結果:' + res)});
這篇文章是自己根據比較正常的思路來寫的一個簡單的promise。
接下來還會有篇文章來自習研究下美團那篇博客以及一些主流的promise的寫法,敬請期待。
參考:
先寫到這里,順便給個github的傳送門,喜歡的朋友star一下啊,自己平時遇到的問題以及一下學習的經歷及寫代碼的思考都會在github上進行記錄~
