歡迎光臨
每天分享高質量文章

Javascript的異步和回呼


作者:Lemures的博客

網址:http://segmentfault.com/a/1190000002999668

點擊“閱讀原文”可查看本文網頁版


引子

每個故事都有由來。前兩天在看 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的異步有哪些方法。這適合高級選手。我剛開始連啥是異步,啥是回呼都不太清楚,這些方法自然也比較難理解。所以還是打好基礎,先弄清楚異步、回呼這些基本概念比較好。

同步與異步

先看個例子。

foo();

bar();

程式運行一般是同步的(synchronous),即按照書寫的順序執行。在上述例子中,bar 方法會在 foo 方法執行完之後,再執行。

異步(asynchronous)與同步相對,即在前一個方法未執行完時,就開始運行後一個方法。在上述例子中,先執行 foo 方法,foo 方法沒執行完,就開始執行 bar 方法。

總而言之,同步就是順序執行,異步就是不完全按順序執行。

異步的好處

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

阻塞與非阻塞

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

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

單執行緒與多執行緒

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

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

多執行緒、非阻塞、異步

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

JS 中的異步

有了上面的一些基本概念,那麼下麵來說說正題,JS中的異步。

我們都知道 JS 是一個單執行緒的語言,永遠只有一個通道在運行程式。那麼既然它是個單執行緒又如何會有異步呢?

JS 中所謂的異步,應該被稱為偽異步(pseudo asynchronous)。這是因為 JS 語言中的異步,會產生阻塞,並會相互干擾。

模擬 JS 中異步的方法 —— setTimeout

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

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 中異步的方法存在的問題 —— 阻塞與干擾

我們將上述代碼塊稍做修改

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

var foo = function(callback){

// foo method

callback();

};

foo(bar);

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

To be Continued

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

赞(0)

分享創造快樂