1. 程式人生 > >js異步處理

js異步處理

情況下 兩數相加 網絡請求 follow cte auto 隊列 內核 回調函數

一、什麽是異步?

我們一般喜歡把異步和同步、並行拿出來比較,我以前的理解總是很模糊,總是生硬地記著“同步就是排隊執行,異步就是一起執行”,現在一看,當初簡直就是傻,所以我們第一步先把這三個概念搞清楚,我不太喜歡看網上有些博客裏很含糊地說“xxxx是同步,xxxx是異步”,還有舉什麽通俗的例子,其實對不懂的人來說還是懵逼。

首先我們要知道這一切的根源都是“Javascript是單線程”,也就是一次只能做一件事,那麽為什麽是單線程呢?因為js渲染在瀏覽器上,包含了許多與用戶的交互,如果是多線程,那麽試想一個場景:一個線程在某個DOM上添加內容,而另一個線程刪除這個DOM,那麽瀏覽器要如何反應呢?這就亂套了。

單線程下所有的任務都是需要排隊的,而這些任務分為兩種:同步任務和異步任務,同步任務就是在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;異步任務指的是,不進入主線程、而進入任務隊列(task queue)的任務,只有任務隊列通知主線程,某個異步任務可以執行了,該任務才會進入主線程執行。所以說同步執行其實也是一種只有主線程的異步執行。這裏有一個視頻關於異步操作是如何被執行的,講得非常好《what the hack is event loop》,我給大家畫個圖再來理解一下。

技術分享圖片 event-loop.png

這裏補充說明下不同的異步操作添加到任務隊列的時機不同,如 onclick, setTimeout, ajax 處理的方式都不同,這些異步操作是由瀏覽器內核的 webcore 來執行的,webcore 包含上面提到的3種 webAPI,分別是 DOM Binding、timer、network模塊。


onclick 由瀏覽器內核的 DOM Binding 模塊來處理,當事件觸發的時候,回調函數會立即添加到任務隊列中。
setTimeout 會由瀏覽器內核的 timer 模塊來進行延時處理,當時間到達的時候,才會將回調函數添加到任務隊列中。
ajax 則會由瀏覽器內核的 network 模塊來處理,在網絡請求完成返回之後,才將回調添加到任務隊列中。
最後再來說下並行,並行是關於能夠同時發生的事情,是一種多線程的運行機制,而不管同步異步都是單線程的。

二、為什麽要用異步操作

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

三、如何實現異步

我們可以通過回調函數Promise生成器Async/Await等來實現異步。
今天我們先說最基礎的回調函數處理方法來實現,列舉幾個大家熟悉的使用場景,比如:ajax請求、IO操作、定時器。

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

回調本身是比較好用的,但是隨著Javascript越來越成熟,對於異步編程領域的發展,回調已經不夠用了,體現在以下幾點:

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

//回調函數實現兩數相加
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的寫法更順序話一些。
2、回調一般會把控制權交給第三方,從而帶來信任問題,比如:

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

而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

js異步處理