自從Node的7.6版本,已經默認支持async/await特性了。若是你尚未使用過他,或者對他的用法不太瞭解,這篇文章會告訴你爲何這個特性「不容錯過」。本文輔以大量實例,相信你能很輕鬆的看懂,並瞭解Javascript處理異步的一大殺器。javascript
文章靈感和內容借鑑了6 Reasons Why JavaScript’s Async/Await Blows Promises Away (Tutorial),英文好的同窗能夠直接戳原版參考。java
對於還不瞭解Async/await特性的同窗,下面一段是一個「速成」培訓。
Async/await 是Javascript編寫異步程序的新方法。以往的異步方法無外乎回調函數和Promise。可是Async/await創建於Promise之上。對於Javascript處理異步,是個老生常談卻歷久彌新的話題:git
從最先的回調函數,到 Promise 對象,再到 Generator 函數,每次都有所改進,但又讓人以爲不完全。它們都有額外的複雜性,都須要理解抽象的底層運行機制。
異步編程的最高境界,就是根本不用關心它是否是異步。程序員
async 函數就是隧道盡頭的亮光,不少人認爲它是異步操做的終極解決方案。github
試想一下,咱們有一個getJSON方法,該方法發送一個異步請求JSON數據,並返回一個promise對象。這個promise對象的resolve方法傳遞異步得到的JSON數據。具體例子的使用以下:編程
const makeRequest = () => getJSON() .then(data => { console.log(data) return "done" }) makeRequest()
在使用async/await時,寫法以下:數組
const makeRequest = async () => { console.log(await getJSON()) return "done" } makeRequest()
對比兩種寫法,針對第二種,我須要進一步說明:promise
1)第二種寫法(使用async/await),在主體函數以前使用了async關鍵字。在函數體內,使用了await關鍵字。固然await關鍵字只能出如今用async聲明的函數體內。該函數會隱式地返回一個Promise對象,函數體內的return值,將會做爲這個Promise對象resolve時的參數。
可使用then方法添加回調函數。當函數執行的時候,一旦遇到await就會先返回,等到異步操做完成,再接着執行函數體內後面的語句。服務器
2)示例中,await getJSON() 說明console.log的調用,會等到getJSON()返回的promise對象resolve以後觸發。app
咱們在看一個例子增強一下理解,該例子取自阮一峯大神的《ECMAScript 6 入門》一書:
function timeout(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } async function asyncPrint(value, ms) { await timeout(ms); console.log(value); } asyncPrint('hello world', 50);
上面代碼指定50毫秒之後,輸出hello world。
那麼,一樣是處理異步操做,Async/await究竟好在哪裏呢?
咱們總結出如下6點。
咱們看一下上面兩處代碼的代碼量,就能夠直觀地看出使用Async/await對於代碼量的節省是很明顯的。對比Promise,咱們不須要書寫.then,不須要新建一個匿名函數處理響應,也不須要再把數據賦值給一個咱們其實並不須要的變量。一樣,咱們避免了耦合的出現。這些看似很小的優點實際上是很直觀的,在下面的代碼示例中,將會更加放大。
Async/await使得處理同步+異步錯誤成爲了現實。咱們一樣使用try/catch結構,可是在promises的狀況下,try/catch難以處理在JSON.parse過程當中的問題,緣由是這個錯誤發生在Promise內部。想要處理這種狀況下的錯誤,咱們只能再嵌套一層try/catch,就像這樣:
const makeRequest = () => { try { getJSON() .then(result => { // this parse may fail const data = JSON.parse(result) console.log(data) }) // uncomment this block to handle asynchronous errors // .catch((err) => { // console.log(err) // }) } catch (err) { console.log(err) } }
可是,若是用async/await處理,一切變得簡單,解析中的錯誤也能垂手可得的解決:
const makeRequest = async () => { try { // this parse may fail const data = JSON.parse(await getJSON()) console.log(data) } catch (err) { console.log(err) } }
想象一下這樣的業務需求:咱們須要先拉取數據,而後根據獲得的數據判斷是否輸出此數據,或者根據數據內容拉取更多的信息。以下:
const makeRequest = () => { return getJSON() .then(data => { if (data.needsAnotherRequest) { return makeAnotherRequest(data) .then(moreData => { console.log(moreData) return moreData }) } else { console.log(data) return data } }) }
這樣的代碼會讓咱們看的頭疼。這這麼多層(6層)嵌套過程當中,很是容易「丟失自我」。
使用async/await,咱們就能夠垂手可得的寫出可讀性更高的代碼:
const makeRequest = async () => { const data = await getJSON() if (data.needsAnotherRequest) { const moreData = await makeAnotherRequest(data); console.log(moreData) return moreData } else { console.log(data) return data } }
一個常常出現的場景是,咱們先調起promise1,而後根據返回值,調用promise2,以後再根據這兩個Promises得值,調取promise3。使用Promise,咱們不難實現:
const makeRequest = () => { return promise1() .then(value1 => { // do something return promise2(value1) .then(value2 => { // do something return promise3(value1, value2) }) }) }
若是你難以忍受這樣的代碼,咱們能夠優化咱們的Promise,方案是使用Promise.all來避免很深的嵌套。
就像這樣:
const makeRequest = () => { return promise1() .then(value1 => { // do something return Promise.all([value1, promise2(value1)]) }) .then(([value1, value2]) => { // do something return promise3(value1, value2) }) }
Promise.all這個方法犧牲了語義性,可是獲得了更好的可讀性。
可是其實,把value1 & value2一塊兒放到一個數組中,是很「蛋疼」的,某種意義上也是多餘的。
一樣的場景,使用async/await會很是簡單:
const makeRequest = async () => { const value1 = await promise1() const value2 = await promise2(value1) return promise3(value1, value2) }
想象一下咱們鏈式調用了不少promises,一級接一級。緊接着,這條promises鏈中某處出錯,以下:
const makeRequest = () => { return callAPromise() .then(() => callAPromise()) .then(() => callAPromise()) .then(() => callAPromise()) .then(() => callAPromise()) .then(() => { throw new Error("oops"); }) } makeRequest() .catch(err => { console.log(err); // output // Error: oops at callAPromise.then.then.then.then.then (index.js:8:13) })
此鏈條的錯誤堆棧信息並沒用線索指示錯誤到底出如今哪裏。更糟糕的事,他還會誤導開發者:錯誤信息中惟一出現的函數名稱其實根本就是無辜的。
咱們再看一下async/await的展示:
const makeRequest = async () => { await callAPromise() await callAPromise() await callAPromise() await callAPromise() await callAPromise() throw new Error("oops"); } makeRequest() .catch(err => { console.log(err); // output // Error: oops at makeRequest (index.js:7:9) })
也許這樣的對比,對於在本地開發階段區別不是很大。可是想象一下在服務器端,線上代碼的錯誤日誌狀況下,將會變得很是有意義。你必定會以爲上面這樣的錯誤信息,比「錯誤出自一個then的then的then。。。」有用的多。
最後一點,可是也是很重要的一點,使用async/await來debug會變得很是簡單。
在一個返回表達式的箭頭函數中,咱們不能設置斷點,這就會形成下面的局面:
const makeRequest = () => { return callAPromise() .then(()=>callAPromise()) .then(()=>callAPromise()) .then(()=>callAPromise()) .then(()=>callAPromise()) }
咱們沒法在每一行設置斷點。可是使用async/await時:
const makeRequest = async () => { await callAPromise() await callAPromise() await callAPromise() await callAPromise() }
Async/await是近些年來JavaScript最具革命性的新特性之一。他讓讀者意識到使用Promise存在的一些問題,並提供了自身來代替Promise的方案。
固然,對這個新特性也有必定的擔憂,體如今:
他使得異步代碼變的再也不明顯,咱們好不容易已經學會並習慣了使用回調函數或者.then來處理異步。新的特性固然須要時間成本去學習和體會;
退回來講,熟悉C#語言的程序員必定會懂得這些學習成本是徹底值得的。
Happy Coding!
PS: 做者Github倉庫,歡迎經過代碼各類形式交流。