前言
昨天在總結javascript異步編程的時候,提到了promise和目前比較流行的async模塊,不過,在比較這兩個解決方案的時候,在我個人的認知上感覺兩個沒有什么太大的本質區別,於是去請教了一些前輩來解答兩個不同方案的優劣,在解答的過程中,涉及到了對一些異步原理的部分。
然后,感覺自己整個人的三觀都被刷新了。
在了解了一些原理相關的內容之后,發現自己所理解的異步太浮於表面,這和網上看到的一些人的一些技術文章有很大的關系,關於網上發布的一些所謂的技術文章,大多都沒有什么權威性,大部分都是個人在某個階段中所理解的內容,而我對異步的理解也被大量的這類文章所影響,並且深信不疑。
為了最終去了結異步到底是怎樣的一個過程,以及異步所產生的初衷,我去尋找了一些比較具有權威性質的文章,然后也請教了一些對於這方面研究比較深入的前輩。
淺談異步
對於異步,不知道是從哪里第一次接觸到的這個詞語,我第一次接觸到異步,是源自jquery的ajax,jquery對於ajax請求開放了兩種方式,一種是同步,一種是異步,而我第一次對於異步的了解就是通過ajax的異步請求。
現在,網上大多數對於異步模式產生原因的解釋大多都是這樣的:因為javascript的執行環境為單線程,所以程序在執行的時候只能按照順序執行,而其中一個程序發生太大的資源消耗,會導致后面的程序過長的等待,造成阻塞的情況。
其實,乍一看,這樣的解釋似乎也沒有什么問題,因為在javascript的編程體驗上,這樣的解釋是比較符合開發感受的,久而久之,很多人對於異步的理解就和我一樣變成了,為了解決兩個需要同時執行的任務這樣的理解。
不過,其實將這個想法往深追究其實是站不住腳的,因為不管是采用了怎樣的機制,javascript所運行的環境已然是單線程,這是不可改變的事實(worker模塊除外),這也就導致了不管表面上是怎樣的,實際內部兩個任務是不可能同時執行的,在任務隊列中,不管怎樣都是會有一個執行順序的。
而也正是因為這樣,異步其實並不能本質上解決程序阻塞的問題。
舉個例子:
function
bendan (callback) {
callback();
}
bendan(() => {
console.log(1);
for
(
var
i = 0; i < 100000000; i++) {
console.log(2);
}
});
console.log(3);
|
上面這段代碼是一個比較好理解的異步過程,如果按照錯誤的異步理解方式,那么這個程序運行的結果應該是可以將3順利的打出,甚至應該在打印2這個比較耗費資源的過程之前就打印出3,但是,實際上的結果是,這段程序在打印2的時候就發生了阻塞,3並不能順利的打印出來。
這就是異步和多線程最本質的區別,也就此說明了,異步本質上其實解決不來資源消耗所發生的阻塞問題。
異步原理
那么,異步到底是為了解決什么樣的問題的呢?或者說他究竟是怎樣的情況下被發明出來的呢?
這要從javascript的特性以及發展說起,javascript這個語言在發明的過程中,其實考慮的並不是很完善,因此有很多地方和最初設想的不太一樣,比較典型的例子就是閉包,閉包的產生其實最初只是因為javascript垃圾回收機制的一個缺陷所產生的一個問題,但是卻意外地變成了一個彩蛋,類似這樣的有很多,只不過沒有其他作用的變成了坑,而可以在其他方面有發展的則變成了彩蛋。
異步其實就是如此。
javascript有一個其他語言所不太具備的方便設計,回調,當然,其他的語言現在也可以實現回調,只不過回調的實現成本比較高,就比如java之前不支持回調,如果想要實現回調的相關效果,需要定義個interface,a去賦值,b函數接收他,這個回調函數還是個局部變量,a改了,b里的也改了,然后b里再去執行,這樣很繁瑣。
但是javascript不一樣,javascript在設計中本身就對回調進行了支持,而在往下看,回調產生了一個很有趣的東西,那就是閉包,而閉包把自身的作用域暴露給了其它對象,什么意思呢?就是說它自身也不知道什么時候會被執行,所以產生了異步,如果這個function,用了函數局部變量,那傳到外面后執行也能拿到,為什么,因為作用域出去了,就是閉包,所以promise,async是為了解決這種問題,讓回調函數被執行完了,再往下執行。
也就是說,異步其實在最初只是為了使用回調所產生了一個結果。
那么為什么異步就變成了彩蛋,然后現在被打大量的使用呢?還有,異步的具體使用場景究竟是什么呢?
這要從程序運行的資源消耗說起,首先要明確一個概念,運行時間長並不代表所消耗的資源多,舉個簡單的例子,ajax請求,在這樣的請求中會有較大的時間開銷,但是這一過程中並沒有太大的資源消耗,或者說對於javascript所執行的環境來說幾乎沒有什么資源的消耗。
所以在這樣的過程中,其實主線程是空閑着的,如果不是異步的行為,那它所做的只有一件事,那就是等待,等待請求的響應,那么,其實在這樣的一個過程中,完全可以把主線程在等待時候的資源開放出來進行其他的操作,這就形成了異步。
雖然這樣的一種行為,和多線程看似一樣,因為都是在前面的程序沒有執行完的時候就開始執行了下面的程序,但是實際上卻是有着本質的區別,如果不是像這種等待的操作,而是運算量比較大的那種,單線程會卡住,而這種情況只有多線程能解決,異步不能。
然后,javascript是怎樣做到利用這個等待資源的呢,這和javascript的運行機制有關,它會把需要執行的放在一個執行隊列中,像是這種需要等待的程序,他在會在執行了之后將他排在后面,然后執行其他的任務,指導它等待結束之后,執行它的回調。
對於這樣一個過程的具體實現,要引入一個叫做幀的概念,對於同步代碼是在當前幀運行的,異步代碼是在下一幀運行的。
盜用一個別人的講解圖。
這樣就能簡單的理解這個運行過程。
總結
首先,最大的感觸就是,對於網絡上的一些技術文章要有判斷能力,雖然說這是可以獲取很多新技術的最有效的途徑,但是很多技術文章也都是個人的見解,他們說出的一些不具有權威性,也不需要負任何的責任,其次,最好還是多看書,大部分可以出版的內容還是具有一些權威性的,至少要比網上的要好。
其實對於異步的理解對於業務相關影響不是特別大,但是像是這樣的理解偏差可能會造成一些不可預計的坑,所以能了解本質的話還是要盡量的追溯到事情的本質。
然后,現在nodejs已經可以進行多線程的編寫,感興趣的可以去找一些worker。
最后,對於這個的理解之后,我明白了promise和async的區別,async所解決的是實現了同步的寫法執行異步操作,更清晰易懂的通過編寫同步代碼實現異步操作,對於async的理解可以再稍微看看generator,應該會有更深的理解。
轉自 wanglanye