今天一時興起,寫了一個漸進升級的異步調用demo,記錄一下。
1. 最基礎的同步調用
//需求:f2在f1之后執行,且依賴f1的返回值。如下: function f1(){ var s="1"; return s; } function f2(s){ s+="-2"; console.log(s); } f2(f1()); //"1-2"
2. 引入異步回調
//繼續,如果f1是個耗時操作,業務上需要做成異步,那么就需要引入回調,如下: function f1(){ var s; setTimeout(function(){ s="1"; f2(s); },1000); } function f2(s){ s+="-2"; console.log(s); } f1(); //"1-2"
3. 回調函數名解耦
//對上面代碼,對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里修改對他的引用了
4. 更多層級的異步回調
//那么,我們繼續,如果業務中引入了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()樣子的清晰的調用呢?
4.1 試着優雅一點
//思來想去,看起來需要引入更多的傳參,那搞兩個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,邏輯有些冗余。
5. 觀察者模式拉平回調
重新思考下,看起來逐級依賴的函數回調,隨着層級的加深,在傳參和調用上都越來越吃力了。
我們現在想辦法拉平一下這些回調,用自定義事件改造下。
專業術語上,叫觀察者模式,即通過自定義事件的監聽和觸發,來實現函數的依賴調用(f1觸發f2的調用)
//注冊自定義事件 拆解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.html
6. Promise的實現
上面的觀察者模式,看起來比較優雅了,但是因為拉平幾個有依賴關系的回調函數,就去注冊一些自定義事件,還是感覺有點怪。
並且,在調用上,只是寫了一個f1(),並不能在調用上看出三個函數的依賴關系。
ES6開始,引入了Promise概念,專門用來處理異步操作問題,參考:http://es6.ruanyifeng.com/#docs/promise
//繼續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); }) //看起來,還不錯哦