轉自: 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上進行記錄~