[譯] 深刻理解 Promise 五部曲:1. 異步問題

原文地址:http://blog.getify.com/promis...javascript

在微博上看到有人分享LabJS做者寫的關於Promise的博客,看了下以爲寫得很好,分五個部分講解了Promise的前因後果。從這篇文章開始,我會陸續把五篇博客翻譯出來跟你們分享,在大牛的帶領下真正理解Promise。賣個關子,做者看待Promise的角度跟我一直以來看到的講解Promise的角度徹底不同,不僅是定留在解決回調金字塔上,至少我沒想到Promise居然有這麼重要的意義。先上第一篇。java

在這篇文章中,我會解釋咱們爲何須要使用一個更好的方式(好比Promise)來進行異步流程的編寫。編程

異步

你確定據說過Javascript中的異步編程,可是它究竟是什麼呢?segmentfault

好比當你發生一個Ajax請求,你一般會提供一個回調函數,這個回調函數會在請求返回的時候被調用。可是你是否思考過你的回調函數在其餘代碼也須要運行的時候是如何被調用的呢?若是兩個回調函數同時都要運行會怎樣呢?JS引擎會如何處理這個問題呢?promise

爲了理解異步究竟是什麼,你首先須要理解一個問題:JS引擎是單線程的。這意味着在任何環境中,只有一段JS代碼會被執行。可是什麼叫一段JS代碼呢?總的來講,每一個函數是一個不可分割的片斷或者代碼塊。當JS引擎開始執行一個函數(好比回調函數)時,它就會把這個函數執行完,也就是說只有執行完這段代碼纔會繼續執行後面的代碼。併發

換句話說,JS引擎就像一個主題公園中的遊樂項目,這個項目每次只能一我的玩兒,人們會排成一個長長的隊。你們一個個上去玩兒,下來一個而後再上去一個。若是你要玩兒這個項目你只能在隊尾排隊等待。幸運的是,每一個人都很快就下來了,因此這個隊伍移動得很快。異步

上面說的隊伍在技術上被叫作事件輪詢。它儘量快的進行輪詢,若是事件隊列中有代碼須要執行,它會讓JS引擎執行這段代碼,而後移到下一個須要執行的代碼,或者等待新的代碼進來。異步編程

併發

若是程序在一個時間只有一個任務在執行,這樣明顯是低效並且有限制性的。若是你點擊一個按鈕提交一個表單,而後你的鼠標就會被凍結而且你不能滾動頁面,這個狀況會持續幾秒直到請求返回,這樣確定會帶來不好的用戶體驗。函數

這就是爲何真實的程序會有不少任務在運行而不是就只有一個任務,可是JS引擎是怎麼在單線程的環境下實現的呢?編碼

你應該想到每一個代碼塊運行只要很短的時間,一般不到1毫秒。你一眨眼的時間,JS引擎會執行上千百個這樣的代碼塊。可是並非全部的代碼塊都是爲了執行同一個任務。好比,當你點擊提交按鈕以後,你也能夠點擊導航或者滾動頁面等等。每一個任務都會被分爲不少個原子操做,執行這些原子操做會很是快。

好比:

Task A

  • step1

  • step2

  • step3

  • step4

Task B

  • step1

  • step2

JS引擎確定不能在執行A:1步驟的同時執行B:1。可是Task B不須要等到Task A執行完後再執行,由於引擎能夠在每一個獨立的原子操做之間快速的切換,多是按下面的順序執行的:

  • A:1

  • B:1

  • A:2

  • B:2(Task B完成)

  • A:3

  • A:4(Task A完成)

因此,事實上Task A和Task B是能夠"同時"運行的,經過穿插地執行它們的每一個原子操做,這叫作併發,換句話說,Task A和Task B是併發的。

咱們很容易就會把併發和並行弄混。在真正並行的系統中,你會有多個線程,可能一個線程執行Task A同時另外一個線程執行Task B。這也意味着,A:1的運行不會阻塞B:1的運行。這就好像有主題公園中有兩個分開的遊樂項目,會有兩隊人在排隊,它們互相不影響。

JS事件輪詢是一個簡單的併發模型。它只容許把每一個事件添加到事件隊列的隊尾,而這個隊列是先進先出的。當條件容許時,回調函數就會被運行。

同步狀況下的異步

在JS中編寫異步代碼一個巧妙可是煩惱的問題是JS引擎實際執行代碼的方式跟咱們看上去不大同樣。例如:

makeAjaxRequest(url,function(response){
    alert("Response:" + response) ;
}) ;

你會怎麼描述這段代碼的流程呢?大多數開發者大概會這麼說:

  1. 發送Ajax請求

  2. 等到請求完成的時候,彈出提示框

可是這跟JS引擎實際的執行狀況相比還不夠準確。這個問題主要是由於咱們大腦習慣同步的方式。在上面這個描述中,咱們使用「等到。。。的時候」來解釋,這就也是說咱們會阻塞等待Ajax請求,而後繼續執行後面的程序。

JS在步驟1和步驟2之間不會阻塞。一個更準確的描述上面這段代碼的方式是:

  1. 發送Ajax請求

  2. 註冊回調函數

  3. 繼續向下執行

  4. 在將來某個時間點,驚呼「Oh,我剛纔獲得一個返回!」。如今,返回去執行註冊的那個回調函數。

這兩個解釋的區別彷佛沒什麼大不了的,可是咱們跳過第三步的思考方式是一個大問題。

源代碼是給開發者的而不是計算機的。計算機只關心1和0.有無限種程序能產生同樣的1和0序列。咱們編寫源代碼爲了使得咱們可以以一種有含義而且準確的方式理解代碼是幹嗎的。因爲咱們的大腦很難處理異步,因此咱們須要找出一種更加同步的方式來編寫異步代碼,隱藏具體的異步實現。

例如,若是下面這段代碼能像咱們須要的那樣運行而且不會阻塞,那麼它是否是更好理解了呢?

response = makeAjaxRequest(url) ;
alert("Response:" + response) ;

若是咱們能夠像這樣編碼,那麼咱們就能夠隱藏或者抽象makeAjaxRequest()的異步本質,不須要擔憂具體細節。
換句話說,咱們能使得異步代碼只出如今具體的實現上,把這些煩人的東西埋在屬於它的地方。

總結

咱們尚未解決問題。可是至少咱們知道了問題是什麼:用異步的方式來表達異步的代碼是艱難的,甚至很難用咱們的大腦來理解。

咱們須要的只是一種以同步的代碼來儘量隱藏具體的異步實現的方式,這樣咱們的大腦更好理解。咱們的目標是以同步的方式來編碼而不須要關係它的實現的同步仍是異步。

在第二部分:轉換的問題中,我會着手處理「回調地獄」來解釋這些問題,咱們也將看到Promises是如何搞定它的。

深刻理解Promise五部曲--1.異步問題
深刻理解Promise五部曲--2.轉換問題
深刻理解Promise五部曲--3.可靠性問題
深刻理解Promise五部曲--4.擴展性問題
深刻理解Promise五部曲--5.樂高問題

最後,安利下個人我的博客,歡迎訪問:http://bin-playground.top

相關文章
相關標籤/搜索