原生JS實現Promise


  ES6中Promise可以說很大情況下改善了異步回調的嵌套問題,那么如果我們自己去寫一個類似Promise的庫應該怎么去寫?

  我們先看一下Promise的特點:

  第一:Promise構造函數接受一個函數作為參數,函數里面有兩個參數resolve和reject分別作為執行成功或者執行失敗的函數

var promise=new Promsie(function(resolve,rejec){ if(/*異步執行成功*/){ resolve(value); }else{ reject(error); } })

  第二:可以通過then設置操作成功之后的操作,接受兩個函數作為參數

.then(function(){ //回調執行成功之后的操作
},function(){ //回調執行失敗之后的操作,可以沒有
});

  那么原生js實現以上特點應該比較簡單了

function PromiseM(){ this.status='pending'; this.msg=''; var process=arguments[0]; var that=this; process(function(){ that.status='resolve'; that.msg=arguments[0]; },function(){ that.status='reject'; that.msg=arguments[0]; }); return this; } PromiseM.prototype.then=function(){ if(this.status=='resolve'){ arguments[0](this.msg); } if(this.status=='reject'&&arguments[1]){ arguments[1](this.msg); } } //測試用例
var mm=new PromiseM(function(resolve,reject){ resolve('123');//123其實就是第二個arguments[0] });//上面的第一個arguments[0] mm.then(function(success){ console.log(success);//該success其實就是上面的this.msg console.log("ok!"); },function(){ console.log('fail!'); });
//123
//ok

  以上只是最基本的實現,在代碼結構結構和容錯方面沒有進行考慮。

  對ES6里promise對象以及異步機制的理解

  我現在聲明一個a,並且要延時1秒鍾后給他賦值為10,然后打印這個a:

var a; setTimeout(function (){ a = 10; },1000);//1000ms后給a賦值為10
console.log(a)//undefined,

  很顯然,a是undefined,因為js是非阻塞的,它不會等你定時器里的東西走完了,再執行打印a這個操作。它不等你讀完,就會迫不及待地去打印a,因為a要1秒后才能被賦值,所以此時a只是個undefined。

  那怎么辦?很簡單,把打印a這個操作也放進延時定時器里嘛,我想這個例子並不難理解,那么接下來我會試圖將其功能進行拓展,打印a這個需求我並不需要,我想要用a做其他事情,比如讓他乘以2並輸出返回結果,或者其他的涉及到a的操作。那很簡單,我將console.log(a)這段代碼刪去,封裝成一個函數,並將其作為參數傳進這個定時器里,也就是所謂的“回調函數”如下:

var a; function foo(callBack){ setTimeout(function (){
    a = 10;     callBack
&&callBack()   },1000) } foo(function (){ //因為a是全局的,所以不需要將a傳參進foo的回調中   return a*2 })

  調用foo的時候,作為參數的這個匿名函數,就是所謂的回調函數,回調函數這個機制極大地增加了代碼的可能性,你可以在回調中做任何事,並且在不同的地方進行調用,你也將得到不同的反饋,如果將一個函數執行過程看成一個時間軸,那回調函數就可以允許你在這個過程中的各個時刻內執行其他代碼,例如你可以將Vue.js的生命周期鈎子函數看成是不同時期的回調函數

  回調函數的作用顯而易見,但如果一層回調套一層回調的話,代碼極其不方便閱讀與修改,因此ES6提供了Promise構造函數,從形式上改善了回調地獄。

var a; var p = new Promise(function (resolve,reject){   setTimeout(function (){     a = 10;     resolve();/*reject也就是失敗時對應的函數,由於這個例子比較簡單,就不再贅述,畢竟是面向新手的*/   },1000) }).then(function (){   console.log(a) })

  現在我們就以一個人起床后的行為為例來深入理解異步執行機制,來看代碼:

getup();
wearClothes();
brushTeeth();
washFace();
eatBreakfast();
goToCompany();

  因為js是異步執行的,所以如果這么寫,邏輯上等同於一個人同時開始做“起床+穿衣+刷牙+洗臉+吃早飯+出門上班”這些事,顯然是不可能的事情,與我們想要的結果大相徑庭,依照回調函數的寫法,我們會在上述每個行為結束之后(即函數執行完畢的我們想要讓他做什么,例如$.ajax()的success)傳入一個回調函數,並將下一步的函數寫進回調里,環環相扣,寫法比較惡心,相當難以維護。

var p = new Promise(function (resolve){ getUp(function (){ resolve(); }); }).then(function (){ return new Promise(function (resolve){ wearClothes(function (){ resolve(); }); }) }).then(function (){ return new Promise(function (resolve){ brushTeeth(function (){ resolve(); }); }) }).then(function (){ return new Promise(function (resolve){ washFace(function (){ resolve(); }); }) }).then(function (){ return new Promise(function (resolve){ eatBreakfast(function (){ resolve(); }); }) }).then(function (){ goToCompany() })

  如果要鏈式調用then的話,必須要注意的一點是,一定要在then里的回調函數return一個新的promise對象,否則執行順序將向上開始查找,直到找到最近的promise實例。

  寫到這里大家會發現promise並沒有改變什么,只是把異步代碼的書寫形式從水平方向改為了豎直方向,讓代碼更符合人類的閱讀習慣


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM