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

我對異步的好奇心起于學習Promise時老是一知半解,最近在看《你所不知道的js(中)》,書中對異步這部分的講解還是很到位的,所以結合自己的理解整理一下相關知識點。
本文將從是什么、為什么、怎么樣這三步式來講這個問題。

一、什么是異步?

我們一般喜歡把異步和同步、并行拿出來比較,我以前的理解總是很模糊,總是生硬地記著“同步就是排隊執行,異步就是一起執行”,現在一看,當初簡直就是傻,所以我們第一步先把這三個概念搞清楚,我不太喜歡看網上有些博客里很含糊地說“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(上)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內容