js異步處理

1、什麼是異步?

咱們通常喜歡把異步和同步、並行拿出來比較,我之前的理解老是很模糊,老是生硬地記着「同步就是排隊執行,異步就是一塊兒執行」,如今一看,當初簡直就是傻,因此咱們第一步先把這三個概念搞清楚,我不太喜歡看網上有些博客裏很含糊地說「xxxx是同步,xxxx是異步」,還有舉什麼通俗的例子,其實對不懂的人來講仍是懵逼。javascript

首先咱們要知道這一切的根源都是「Javascript是單線程」,也就是一次只能作一件事,那麼爲何是單線程呢?由於js渲染在瀏覽器上,包含了許多與用戶的交互,若是是多線程,那麼試想一個場景:一個線程在某個DOM上添加內容,而另外一個線程刪除這個DOM,那麼瀏覽器要如何反應呢?這就亂套了。html

單線程下全部的任務都是須要排隊的,而這些任務分爲兩種:同步任務和異步任務,同步任務就是在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;異步任務指的是,不進入主線程、而進入任務隊列(task queue)的任務,只有任務隊列通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行。因此說同步執行其實也是一種只有主線程的異步執行。這裏有一個視頻關於異步操做是如何被執行的,講得很是好《what the hack is event loop》,我給你們畫個圖再來理解一下。java

event-loop.png

這裏補充說明下不一樣的異步操做添加到任務隊列的時機不一樣,如 onclick, setTimeout, ajax 處理的方式都不一樣,這些異步操做是由瀏覽器內核的 webcore 來執行的,webcore 包含上面提到的3種 webAPI,分別是 DOM Binding、timer、network模塊。
onclick 由瀏覽器內核的 DOM Binding 模塊來處理,當事件觸發的時候,回調函數會當即添加到任務隊列中。
setTimeout 會由瀏覽器內核的 timer 模塊來進行延時處理,當時間到達的時候,纔會將回調函數添加到任務隊列中。
ajax 則會由瀏覽器內核的 network 模塊來處理,在網絡請求完成返回以後,纔將回調添加到任務隊列中。
最後再來講下並行,並行是關於可以同時發生的事情,是一種多線程的運行機制,而無論同步異步都是單線程的。git

2、爲何要用異步操做

這個很好理解,同步下前一個事件執行完了才能執行後一個事件,那麼要是遇到Ajax請求這種耗時很長的,那頁面在這段時間就無法操做了,卡在那兒,更有甚者,萬一這個請求因爲某種緣由一直沒有完成,那頁面就block了,很不友好。github

3、如何實現異步

咱們能夠經過回調函數Promise生成器Async/Await等來實現異步。
今天咱們先說最基礎的回調函數處理方法來實現,列舉幾個你們熟悉的使用場景,好比:ajax請求、IO操做、定時器。web

ajax(url, function(){ //這就是回調函數 }); setTimeOut(function(){ //回調函數 }, 1000) 

回調自己是比較好用的,可是隨着Javascript愈來愈成熟,對於異步編程領域的發展,回調已經不夠用了,體如今如下幾點:ajax

一、大腦處理程序是順序的,對於複雜的回調函數會不易理解,咱們須要一種更同步、更順序的方式來表達異步。
舉例說明:編程

//回調函數實現兩數相加 function add(getX, getY, cb){ var x, y; getX(function(xVal){ x=xVal; if(y!=undefined){ cb(x+y); } }); getY(function(){ y=yVal; if(x!=undefined){ cb(x+y); } }); } add(fetchX, fetchY, function(sum){ console.log(sum); }) //Promise實現兩數相加 function add(xPromise, yPromise){ return Promise.all([xPromise, yPromise]) .then(function(values){ return value[0] + value[1]; }); } //fetchX()、fetchY()返回相應值的Promise add(fetchX(), fetchY()) .then(function(sum){ console.log(sum); }) 

只看結構是否是Promise的寫法更順序話一些。
二、回調通常會把控制權交給第三方,從而帶來信任問題,好比:瀏覽器

  • 調用回調過早
  • 調用回調過晚(或未調用)
  • 調用回調次數過多或過少
  • 未能傳遞所需的環境和參數
  • 吞掉可能出現的錯誤和異常

而Promise的特性就有效地解決了這些問題,它是如何解決的呢?網絡

調用回調過早

這種顧慮主要是代碼是否會引入類Zalgo效應,也就是一個任務有時會同步完地成,而有時會異步地完成,這將致使竟合狀態。
Promise被定義爲不能受這種顧慮的影響,由於即使是當即完成的Promise(好比 new Promise(function(resolve){ resolve(42); }))也不可能被同步地 監聽。也就是說,但你在Promise上調用then(..)的時候,即使這個Promise已經被解析了,你給then(..)提供的回調也將老是被異步地調用。

調用回調過晚

當一個Promise被調用時,這個Promise 上的then註冊的回調函數都會在下一個異步時機點上,按順序地,被當即調用。這些回調中的任意一個都沒法影響或延誤對其它回調的調用。
舉例說明:

p.then( function(){ p.then( function(){ console.log( "C" ); } ); console.log( "A" ); } ); p.then( function(){ console.log( "B" ); } ); // A B C 

爲何「C」沒有排到「B」的前面?由於由於「C」所處的.then回調函數是在下一個事件循環tick。

回調未調用

這是一個很常見的顧慮。Promise用幾種方式解決它。
首先,當Promise被解析後,在代碼不出錯的狀況下它必定會告知你解析結果。若是代碼有錯誤,歸類於後面的「吞掉錯誤或異常」中。
那若是Promise自己無論怎樣永遠沒有被解析呢?那麼Promise會用Promise.race來解決。
看代碼示例:

// 一個使Promise超時的工具 function timeoutPromise(delay) { return new Promise( function(resolve,reject){ setTimeout( function(){ reject( "Timeout!" ); }, delay ); } ); } // 爲`foo()`設置一個超時 Promise.race( [ foo(), // 嘗試調用`foo()` timeoutPromise( 3000 ) // 給它3秒鐘 ] ) .then( function(){ // `foo(..)`及時地完成了! }, function(err){ // `foo()`不是被拒絕了,就是它沒有及時完成 // 那麼能夠考察`err`來知道是哪一種狀況 } ); 
調用次數過少或過多

正常是調用一次,「過少」就是未被調用,參考上文;「過多」的狀況也很容易理解。Promise的定義方式使得它只能被決議一次,若是出於某種狀況決議了屢次,Promise也只會接受第一次決議,並忽略後續調用。

未能傳遞所需的參數/環境值

Promise只會有一個解析結果(完成或拒絕)。若是沒有用一個值明確地解析它,它的值就是undefined,就像JS中常見的那樣。

吞掉錯誤或異常

Promise中異常會被捕獲,而且使這個Promise被拒絕。
舉個例子:

var p = new Promise( function(resolve,reject){ foo.bar(); // `foo`沒有定義,因此這是一個錯誤! resolve( 42 ); // 永遠不會跑到這裏 :( } ); p.then( function fulfilled(){ // 永遠不會跑到這裏 :( }, function rejected(err){ // `err`將是一個來自`foo.bar()`那一行的`TypeError`異常對象 } ); 

Promise就先說到這裏,關於PromiseAPI及其源碼還有生成器、Async/Await 在後續文章中整理報道。
【寫得很差的地方請大膽吐槽,很是感謝你們帶我進步。】

參考資料:
阮一峯event-loop
王福朋深刻理解javascript異步系列一
你不知道的javascript
你不懂JS: 異步與性能 第三章: Promise(上)


原做者:程序媛Wendy
連接:https://www.jianshu.com/p/f4abe8c4fc2f
相關文章
相關標籤/搜索