Javascript的非同步和回撥

發表於2015-07-18

引子

每個故事都有由來。前兩天在看 gulp 的時候,看到了它有個 promise 的玩意兒,然後的然後,這兩天就掉進了javascript 的非同步和回撥的坑裡面去了。
其間搜尋了 javascript promise,看到了一堆好文章。大概給個 List 吧。

看得昏天黑地,大概也理清楚了一點,做個小總結。

一些概念

上面這些文章寫得都挺好,但大部分都是上來直接說怎麼非同步回撥,js的非同步有哪些方法。這適合高階選手。我剛開始連啥是非同步,啥是回撥都不太清楚,這些方法自然也比較難理解。所以還是打好基礎,先弄清楚非同步、回撥這些基本概念比較好。

同步與非同步

先看個例子。

  • 程式執行一般是同步的(synchronous),即按照書寫的順序執行。在上述例子中,bar 方法會在 foo 方法執行完之後,再執行。
  • 非同步(asynchronous)與同步相對,即在前一個方法未執行完時,就開始執行後一個方法。在上述例子中,先執行 foo 方法,foo 方法沒執行完,就開始執行 bar 方法。
  • 總而言之,同步就是順序執行,非同步就是不完全按順序執行。

非同步的好處

從非同步的概念中可以發現,程式非同步執行,可以提高程式執行的效率,不必等一個程式跑完,再跑下一個程式,特別當這兩個程式是無關的時候。兩個程式在一定時間內,可以是同時執行的。寫伺服器的時候應該會碰到很多這樣的例子。可以想象,如果伺服器的程式都是同步的,那併發什麼的就不存在了吧。

阻塞與非阻塞

這一點是我自己簡單的理解。

  • 阻塞就是說一個程式沒執行完,它後面的程式是無法執行的。
  • 非阻塞則相反,一個程式如果因為各種原因(網路、程式碼量等)沒執行完的時候,其他的程式也是可以繼續執行的。

單執行緒與多執行緒

這一點也是我自己的簡單理解。

  • 單執行緒是指程式執行只有一個通道,不同的方法需要排隊執行。
  • 而很多語言都可以提供多執行緒的功能,相當於開了幾個通道執行程式,使得程式可以在不同的執行緒中執行,不會相互影響。

多執行緒、非阻塞、非同步

從上述基本概念中可以發現,非同步如果發生在多執行緒語言中,會十分自然且符合邏輯。非同步本質上應該就是多執行緒語言的產物。因為只有在多執行緒語言中才能夠實現程式之間相互不干擾,不產生阻塞。

JS 中的非同步

有了上面的一些基本概念,那麼下面來說說正題,JS中的非同步。
我們都知道 JS 是一個單執行緒的語言,永遠只有一個通道在執行程式。那麼既然它是個單執行緒又如何會有非同步呢?
JS 中所謂的非同步,應該被稱為偽非同步(pseudo asynchronous)。這是因為 JS 語言中的非同步,會產生阻塞,並會相互干擾。

模擬 JS 中非同步的方法 —— setTimeout

我們來看一下 setTimeout 如何模擬 JS 中的非同步。

上述過程執行的時候,會列印出

foo begins
bar executed
foo finishes

所以,在上述程式碼塊中,在前一方法(foo)執行時,後一方法(bar)也可以執行。符合非同步的基本概念,程式並不按順序執行。
說是模擬是因為,你可以把 console.log('foo begins'); 理解成會執行 1 秒的一個程式碼行,執行完後,會跳出foo finishes。而中間這 1 秒執行的時候,後面的 bar 方法也是可以執行的。這樣就模擬了一個非同步的效果。

JS 中非同步的方法存在的問題 —— 阻塞與干擾

我們將上述程式碼塊稍做修改

你會發現 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上這個問答的解釋,可以明確,把一個函式作為引數傳入到另一個函式中,那麼這個作為引數的函式就叫做回撥函式。如:

其中,bar 就是一個回撥函式。當然了,按我個人的理解,應該說是 bar 是 foo 的回撥函式。

To be Continued

時間問題,具體的實現方式和理解還沒好好看,日後再做細細梳理。上述理解如有偏頗,歡迎討論指正。

相關文章