在常規的服務器端程序設計中, 好比說爬蟲程序, 發送http請求的過程會使整個執行過程阻塞,直到http請求響應完成代碼纔會繼續執行, 以php爲例子php
$url = "http://www.google.com.hk"; $result = file_get_contents($url); echo $result;
當代碼執行到第二行時,程序便陷入了等待,直到請求完成,程序纔會繼續往下跑將抓取到的html輸出。這種作法的好處是代碼簡潔明瞭,運行流程清晰, 容易維護。 缺點就是程序的運行速度依賴於http請求的響應時間,影響程序的運行效率。 然而, 由於web程序自己特質的緣由,這種問題是避無可避的,程序依賴於http響應的結果和保證自身的迅速響應二者之間是存在矛盾的, 確定沒法兼顧。html
可是在客戶端程序或者非http應用的場景下是不存在相似的衝突的, 在Java或C#客戶端編程中,碰到這種問題通常都是開啓兩個線程各幹各的。而在JavaScript中,由於語言自己不支持多線程, 因此此類問題是使用回調函數來解決。前端
以最簡單的前端ajax請求爲例node
$.get("data.json", function ( response ) { console.log("2"); }); console.log("1")
代碼先輸出1,再輸出2,整個程序執行流程並未因http請求而被阻塞,回調函數方案完美的把問題解決。 然而,這只是最簡單回調函數示例,假如回調函數嵌套了許多層呢?jquery
$.get("data1.json", function (response) { $.get(response.url, function (response) { $.get(response.url, function (response) { console.log(response); }); }); });
回調嵌套的越深,代碼運行邏輯就越難理清楚, 若是在上面代碼的基礎上再混入一些複雜的業務邏輯,那代碼將會極難維護, 到時候遇到問題了剪不斷理還亂的感受確定會讓人紅着眼睛罵娘。 雖然這種回調嵌套的場景在web前端開發中比較罕見, 但在nodejs服務器端開發領域仍是常見的。 那如何克服這個問題?假如用php來寫, 那即是一件很輕鬆的事了。es6
$response = file_get_contents("data1.json"); $response1 = file_get_contents($response["url"]); $response2 = file_get_contents($response1["url"]); echo $response;
以php發送http請求的方案來實現, 代碼邏輯就清晰了許多。 在古時候 ,JavaScript想以這種方式實現ajax那就是癡人說夢,可是當JavaScript升級至es6版本後,經過特定的途徑也可實現這種寫法。 在網上這種寫法被稱之爲「以同步的方式編寫異步代碼」,可是我以爲這種說法容易把人給搞迷糊,能夠直接把這種寫法稱之爲:「同步寫法」, 由於裏面的異步執行已經被隱藏了起來。 要實現這種寫法必須使用async和await這兩個關鍵字。在兩個關鍵字是es7的範疇, es6還不支持,可是能夠經過特定的工具將使用這兩個關鍵字的代碼轉爲es6的代碼去執行, 好比說TypeScript和babel, 在此文中使用的代碼示例都是由TypeScript實現。對於async和await的底層機制這裏就不詳述了, 以避免將文章的篇幅拖的很長,這裏就講解一下這兩個關鍵字能實現的效果。 先把上面用JavaScript實現的多層嵌套回調用同步的方式來改寫, 代碼以下web
async function ajax(url) { return new Promise(function (resolve, reject) { let ajaxSetting = { url: url, success: function (response) { resolve(response); }, error: function () { reject("請求失敗"); } } $.ajax(ajaxSetting); }); } async function run() { let response1 = await ajax("data1.json"); let response2 = await ajax(response1["url"]); let response3 = await ajax(response2["url"]); console.log(response3); } //不阻塞 run();
代碼由ajax和run這兩個函數組成, ajax是對jquery ajax的封裝,使之能不使用回調函數就能得到ajax的響應結果。 當函數被聲明爲async類型時,若是這個函數要有返回值 ,而且返回值要在某個回調函數中得到,那麼這個函數的返回結果就只能是一個 Promise對象,就像示例的ajax函數同樣,返回值若是是其它類型那就達不到指望的效果。 Promise構造函數的參數是一個函數,resolve和reject分別是這個函數的兩個參數,同時這兩個參數自身也是函數類型,這兩個參數有着重要的意義,在這裏它們的做用就是將ajax的響應內容給返回出去,resolve表示返回正常情況下的值, reject表示返回異常狀態下的值。按照傳統的編碼方式, 能夠將reject看做是拋出了一個異常,像throw "請求失敗", 這樣,在函數調用的外部能夠用try catch進行捕獲。將值傳出去爲何要經過這兩個參數呢?由於沒轍啊, 試想一下,ajax的回調函數中使用return語句, 意義何在?所以也只能變向的經過Promise將返回值扔給外部的調用者。 因此,使用async和await的第一個要點就是ajax
當函數要得到異步結果時,能夠函數聲明爲async類型, 函數的返回值設爲Promise類型對象,而Promise中的resolve和reject是用來向async函數返回結果的, 功效如同普通函數的return語句。編程
async類型函數要怎麼使用呢?有兩種方法,一種是直接調用, 直接調用的話函數前面async關鍵字就被忽略了, 調用函數返回的結果就是一個Promise對象, Promise對像如何使用在這裏不進行深究,大體就是像下面這樣的寫法json
ajax("data1.json").then(function( response ){ …… });
仍是以回調函數的形式出現,改進代碼所帶來的意義並無體現。
另外一種方法是在調用函數時加上await關鍵字,await的意義就在於接收async函數中的Promise對象中resolve和reject傳遞的值 ,並且除非resolve和reject這兩個函數在回調函數中被調用到了, 不然代碼就一直被阻塞在那裏?換句話說, resolve和reject的調用是用來通知await等待結束,代碼能夠繼續執行了。 這種寫法不就是以前千方百計想實現的同步寫法麼?跟php的寫法區別在於多了 await、async、Promise這三個概念, 可是在不考慮其中的內部運行原理的話, 代碼的執行流程上已經和同步的寫法沒一絲區別了。有一點須要注意, 假如須要在函數中使用await調用,那麼這個函數也必須被聲明爲async類型, 不然編譯出錯, 程序沒法正常運行。 因此, 第二個要點就是
await就是用來等待Promise對象中resolve和reject這兩個函數的執行的,而且將這兩個函數傳遞的參數看成返回結果賦給變量,如同run函數中的代碼示例那樣。 別外, await必須被夾在兩個async中間, 一個是await調用的函數,一個是await所在的函數。
至於Promise中的reject,就是用來拋異常的, 在外await調用以外可以使用try catch捕獲,代碼以下
async function run() { try { let response1 = await ajax("data1.json"); let response2 = await ajax(response1["url"]); let response3 = await ajax(response2["url"]); console.log(response3); } catch (ex) { console.log(ex); } }
此文只是純粹的講解 await和async能起什麼樣的做用?如何使用?至於深刻細節方面的知識, 有興趣的同窗能夠去阮一峯的博客裏學習, 附上連接地址