引子
每個故事都有由來。前兩天在看 gulp
的時候,看到了它有個 promise
的玩意兒,然後的然後,這兩天就掉進了javascript
的非同步和回撥的坑裡面去了。
其間搜尋了 javascript promise
,看到了一堆好文章。大概給個 List 吧。
- https://software.intel.com/zh-cn/articles/asynchronized-javascript-pro…
- http://stackoverflow.com/questions/7104474/how-does-asynchronous-javas…
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Glob…
- http://www.ruanyifeng.com/blog/2012/12/asynchronous_javascript.html
- http://krasimirtsonev.com/blog/article/7-lines-JavaScript-library-for-…
- http://segmentfault.com/q/1010000000140970
- http://www.alloyteam.com/2014/05/javascript-promise-mode/
看得昏天黑地,大概也理清楚了一點,做個小總結。
一些概念
上面這些文章寫得都挺好,但大部分都是上來直接說怎麼非同步回撥,js的非同步有哪些方法。這適合高階選手。我剛開始連啥是非同步,啥是回撥都不太清楚,這些方法自然也比較難理解。所以還是打好基礎,先弄清楚非同步、回撥這些基本概念比較好。
同步與非同步
先看個例子。
1 2 |
foo(); bar(); |
- 程式執行一般是
同步
的(synchronous),即按照書寫的順序執行。在上述例子中,bar 方法會在 foo 方法執行完之後,再執行。 非同步
(asynchronous)與同步相對,即在前一個方法未執行完時,就開始執行後一個方法。在上述例子中,先執行 foo 方法,foo 方法沒執行完,就開始執行 bar 方法。- 總而言之,同步就是順序執行,非同步就是不完全按順序執行。
非同步的好處
從非同步的概念中可以發現,程式非同步執行,可以提高程式執行的效率,不必等一個程式跑完,再跑下一個程式,特別當這兩個程式是無關的時候。兩個程式在一定時間內,可以是同時執行的。寫伺服器的時候應該會碰到很多這樣的例子。可以想象,如果伺服器的程式都是同步的,那併發什麼的就不存在了吧。
阻塞與非阻塞
這一點是我自己簡單的理解。
阻塞
就是說一個程式沒執行完,它後面的程式是無法執行的。- 而
非阻塞
則相反,一個程式如果因為各種原因(網路、程式碼量等)沒執行完的時候,其他的程式也是可以繼續執行的。
單執行緒與多執行緒
這一點也是我自己的簡單理解。
單執行緒
是指程式執行只有一個通道,不同的方法需要排隊執行。- 而很多語言都可以提供
多執行緒
的功能,相當於開了幾個通道執行程式,使得程式可以在不同的執行緒中執行,不會相互影響。
多執行緒、非阻塞、非同步
從上述基本概念中可以發現,非同步如果發生在多執行緒語言中,會十分自然且符合邏輯。非同步本質上應該就是多執行緒語言的產物。因為只有在多執行緒語言中才能夠實現程式之間相互不干擾,不產生阻塞。
JS 中的非同步
有了上面的一些基本概念,那麼下面來說說正題,JS中的非同步。
我們都知道 JS 是一個單執行緒的語言,永遠只有一個通道在執行程式。那麼既然它是個單執行緒又如何會有非同步呢?
JS 中所謂的非同步,應該被稱為偽非同步(pseudo asynchronous)。這是因為 JS 語言中的非同步,會產生阻塞,並會相互干擾。
模擬 JS 中非同步的方法 —— setTimeout
我們來看一下 setTimeout 如何模擬 JS 中的非同步。
1 2 3 4 5 6 7 8 9 10 11 |
var foo = function(){ console.log('foo begins'); setTimeout(function(){ console.log('foo finishes'); },1000); }; var bar = function(){ console.log('bar executed'); } foo(); bar(); |
上述過程執行的時候,會列印出
foo begins
bar executed
foo finishes
所以,在上述程式碼塊中,在前一方法(foo)執行時,後一方法(bar)也可以執行。符合非同步的基本概念,程式並不按順序執行。
說是模擬是因為,你可以把 console.log('foo begins');
理解成會執行 1 秒的一個程式碼行,執行完後,會跳出foo finishes
。而中間這 1 秒執行的時候,後面的 bar 方法也是可以執行的。這樣就模擬了一個非同步的效果。
JS 中非同步的方法存在的問題 —— 阻塞與干擾
我們將上述程式碼塊稍做修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var foo = function(){ console.log('foo begins'); setTimeout(function(){ console.log('foo finishes'); },1000); }; var bar = function(){ while(){ } } foo(); bar(); |
你會發現 1 秒之後 foo finishes
並沒有被列印出來。這是因為 bar 方法是個死迴圈,使得 js 引擎假死,導致了 foo 方法也沒有被執行完。如果是多執行緒的非同步,假死的應該是執行 bar 方法的執行緒,而 foo 方法仍然會按預期列印出 foo finishes
。當然了,其實這個死迴圈也只是模擬 bar 方法塊程式執行的時間將很長。實際上,如果 bar 方法執行的時間超過了 1 秒,比方說是 5 秒,那麼 foo finishes
也將在 5 秒之後被列印出來。這個本質上取決於 JS 單執行緒程式塊按佇列執行的特性。
所以 JS 中的非同步並不能像普通的非同步一樣,實現非阻塞和不干擾。
JS 中非同步的一些實現方法
雖然 JS 中的非同步有其先天的缺陷,但是這種非同步的思想,仍然能被 JS 程式開發人員所借鑑。畢竟,非同步是可以大大提高程式執行效率的。
也正是由於 JS 本身是單執行緒程式的關係,所以 JS 中非同步的實現,並不能像其他語言一樣,簡單地多開個執行緒就可以解決。
目前我看的集中方法主要有回撥、事件類方法、promise等。
回撥
先說說回撥是什麼吧。
回撥
(callback)這種名詞就跟函式
(function)一樣,乍一看是比較難懂的,至少我是這樣的。
根據sf上這個問答的解釋,可以明確,把一個函式作為引數傳入到另一個函式中,那麼這個作為引數的函式就叫做回撥函式。如:
1 2 3 4 5 |
var foo = function(callback){ // foo method callback(); }; foo(bar); |
其中,bar 就是一個回撥函式。當然了,按我個人的理解,應該說是 bar 是 foo 的回撥函式。
To be Continued
時間問題,具體的實現方式和理解還沒好好看,日後再做細細梳理。上述理解如有偏頗,歡迎討論指正。