js異步處理(一)——理解異步

簡書地址:http://www.jianshu.com/p/f4abe8c4fc2f
我對異步的好奇心起於學習Promise時總是隻知其一;不知其二,最近在看《你所不知道的js(中)》,書中對異步這部分的講解仍是很到位的,因此結合本身的理解整理一下相關知識點。
本文將從是什麼、爲何、怎麼樣這三步式來說這個問題
1、什麼是異步?
  咱們通常喜歡把異步和同步、並行拿出來比較,我之前的理解老是很模糊,老是生硬地記着「同步就是排隊執行,異步就是一塊兒執行」,如今一看,當初簡直就是傻,因此咱們第一步先把這三個概念搞清楚,我不太喜歡看網上有些博客裏很含糊地說「xxxx是同步,xxxx是異步」,還有舉什麼通俗的例子,其實對不懂的人來講仍是懵逼。
  首先咱們要知道這一切的根源都是「Javascript是單線程」,也就是一次只能作一件事,那麼爲何是單線程呢?由於js渲染在瀏覽器上,包含了許多與用戶的交互,若是是多線程,那麼試想一個場景:一個線程在某個DOM上添加內容,而另外一個線程刪除這個DOM,那麼瀏覽器要如何反應呢?這就亂套了。
  單線程下全部的任務都是須要排隊的,而這些任務分爲兩種:同步任務和異步任務,同步任務就是在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;異步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行。因此說同步執行其實就是一種只有主線程的異步執行。這裏有一個視頻關於異步操做是如何被執行的,講得很是好《 what the hack is event loop》,我給你們畫個圖再來理解一下
最後再來講下並行,並行是關於可以同時發生的事情,是一種多線程的運行機制,而無論同步異步都是單線程的
2、爲何要用異步操做
  這個很好理解,同步下前一個事件執行完了才能執行後一個事件,那麼要是遇到Ajax請求這種耗時很長的,那頁面在這段時間就無法操做了,更有甚者,萬一這個請求因爲某種緣由一直沒有完成,那頁面就block了,很不友好。
3、如何實現異步
  咱們能夠經過回調函數、Promise、生成器、Async/Await等來實現異步。
  今天咱們先說最基礎的回調函數處理方法來實現,列舉幾個你們熟悉的使用場景,好比:ajax請求、IO操做、定時器。
   ajax(url, function(){
      //這就是回調函數
   });
回調自己是比較好用的,可是隨着Javascript愈來愈成熟,對於異步編程領域的發展,回調已經不夠用了,體如今如下幾點:
一、大腦處理程序是順序的,對於複雜的回調函數會不易理解,咱們須要一種更同步、更順序的方式來表達異步。
舉例說明:
//回調函數實現兩數相加
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的寫法更順序話一些。javascript

 
二、回調通常會把控制權交給第三方,從而帶來信任問題,好比:
  • 調用回調過早
  • 調用回調過晚(或未調用)
  • 調用回調次數過多或過少
  • 未能傳遞所需的環境和參數
  • 吞掉可能出現的錯誤和異常
而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 在後續文章中整理報道。
 
【寫得很差的地方請大膽吐槽,很是感謝你們帶我進步。】
參考資料:
 
 
 
 
相關文章
相關標籤/搜索