今天一時興起,寫了一個漸進升級的異步調用demo,記錄一下。javascript
//需求:f2在f1以後執行,且依賴f1的返回值。以下: function f1(){ var s="1"; return s; } function f2(s){ s+="-2"; console.log(s); } f2(f1()); //"1-2"
//繼續,若是f1是個耗時操做,業務上須要作成異步,那麼就須要引入回調,以下: function f1(){ var s; setTimeout(function(){ s="1"; f2(s); },1000); } function f2(s){ s+="-2"; console.log(s); } f1(); //"1-2"
//對上面代碼,對f2作個函數名上的解耦,以下: function f1(callback){ var s; setTimeout(function(){ s="1"; callback(s); },1000); } function f2(s){ s+="-2"; console.log(s); } f1(f2); //"1-2" //這樣,無論之後f2的function name如何變動,咱們都不須要去f1裏修改對他的引用了
//那麼,咱們繼續,若是業務中引入了f3,且逐級依賴異步耗時操做f1和f2。以下: function f1(){ var s; setTimeout(function(){ s="1"; f2(s); },1000); } function f2(s){ setTimeout(function(){ s+="-2"; f3(s); },1000); } function f3(s){ s+="-3"; console.log(s); } f1(); //"1-2-3" //這時,該怎麼對f2和f3的function name解耦,以及怎樣保持一個相似f1().f2().f3()樣子的清晰的調用呢?
//思來想去,看起來須要引入更多的傳參,那搞兩個callback參數吧: function f1(callback1,callback2){ var s; setTimeout(function(){ s="1"; callback1(s,callback2); },1000); } function f2(s,callback){ setTimeout(function(){ s+="-2"; callback(s); },1000); } function f3(s){ s+="-3"; console.log(s); } f1(f2,f3); //"1-2-3" //WTF,尼瑪,這也太尼瑪髒了。函數名雖然解耦了,調用也很清晰。可是一個callback2參數須要在多個function之間傳遞,代碼可讀性變差;而且f1中傳入了並不須要處理的callback2,邏輯有些冗餘。
從新思考下,看起來逐級依賴的函數回調,隨着層級的加深,在傳參和調用上都愈來愈吃力了。
咱們如今想辦法拉平一下這些回調,用自定義事件改造下。
專業術語上,叫觀察者模式,即經過自定義事件的監聽和觸發,來實現函數的依賴調用(f1觸發f2的調用)html
//註冊自定義事件 拆解f1 f2 f3的依賴回調關係致使的代碼邏輯上的嵌套(使用CustomEvent的detail屬性,實現參數傳遞) document.addEventListener("f1:done",function(e){ f2(e.detail); }); document.addEventListener("f2:done",function(e){ f3(e.detail); }); function f1(){ var s; setTimeout(function(){ s="1"; document.dispatchEvent(new CustomEvent('f1:done', {detail:s})); },1000); } function f2(s){ setTimeout(function(){ s+="-2"; document.dispatchEvent(new CustomEvent('f2:done', {detail:s})); },1000); } function f3(s){ s+="-3"; console.log(s); } f1(); //"1-2-3"
注:阮一峯的這篇文章裏,還引入了一個訂閱/發佈模式,我的感受沒什麼意義,核心原理仍是事件註冊,參考:http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.htmljava
上面的觀察者模式,看起來比較優雅了,可是由於拉平幾個有依賴關係的回調函數,就去註冊一些自定義事件,仍是感受有點怪。
而且,在調用上,只是寫了一個f1(),並不能在調用上看出三個函數的依賴關係。
ES6開始,引入了Promise概念,專門用來處理異步操做問題,參考:http://es6.ruanyifeng.com/#docs/promisees6
//繼續Promise方式,試着改寫一下: function f1(){ var p1=new Promise(function(resolve, reject) { var s; setTimeout(function(){ s="1"; resolve(s); },1000); }); return p1; } function f2(s){ var p2=new Promise(function(resolve, reject) { setTimeout(function(){ s+="-2"; resolve(s); },1000); }); return p2; } function f3(s){ s+="-3"; console.log(s); } f1().then(function(s) { return f2(s); }).then(function(s) { f3(s); }) //看起來,還不錯哦