轉自: http://www.jianshu.com/p/473cd754311fjavascript
看了些promise的介紹,仍是感受不夠深刻,這個在解決異步問題上是一個很好的解決方案,因此詳細看一下,順便按照本身的思路實現一個簡單的Promise。html
promise
是一個擁有符合上面的特徵的then方法的對象或者方法。thenable
是定義了then方法的對象或者方法value
是任何合法的js的值(包括undefined,thenable或者promise)exception
是一個被throw申明拋出的值reason
是一個指明瞭爲何promise被拒絕then方法必須也返回一個promise(這個promise能夠是原來的promise,實現必須申明什麼狀況下二者能夠相等)promise2 = promise1.then(onFulfilled, onRejected);java
onFulfilled
和onRejected
都返回一個value x,執行2.3Promise的解決步驟[Resolve]onFulfilled
和onRejected
都拋出exception e,promise2必須被rejected一樣的eonFulfilled
不是個function,且promise1 is fulfilled,promise2也會fulfilled,和promise1的值同樣onRejected
不是個function,且promise1 is rejected,promise2也會rejected,理由和promise1同樣這裏不論promise1被完成仍是被拒絕,promise2 都會被 resolve的,只有出現了一些異常纔會被rejectedgit
promise
和一個值x
做爲輸入的一個抽象操做。若是這個x是支持then的,他會嘗試讓promise接受x的狀態;不然,他會用x的值來fullfill這個promise。運行這樣一個東西,遵循如下的步驟
若是promise產生了環形的嵌套,好比[Resolve]最終喚起了[Resolve],那麼實現建議且並不強求來發現這種循環,而且reject這個promise使用一個TypeError。github
思路都是最正常的思路,想要寫一個Promise,確定得使用一個異步的函數,就拿setTimeout來作。數組
var p = new Promise(function(resolve){ setTimeout(resolve, 100); }); p.then(function(){console.log('success')},function(){console.log('fail')});
上面是個最簡單的使用場景咱們須要慢慢來構建promise
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)});
我按照正常思路這麼寫的時候發現出了點問題,由於按照最上面的規範。即便一個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上進行記錄~