ES2017 在 6 月最終敲定了,隨之而來的是普遍的支持了我最喜歡的最喜歡的JavaScript功能: async
(異步) 函數。若是你也曾爲異步 Javascript 而頭疼,那麼這個就是爲你設計的。若是你沒有的話,那麼你有多是個天才。javascript
Async(異步) 函數或多或少容許你編寫順序的 JavaScript 代碼,而無需將全部邏輯包裝在 callbacks(回調),generators(生成器) 或 promises 中。 考慮一下這個代碼:java
function logger() { let data = fetch('http://sampleapi.com/posts') console.log(data) } logger()
這段代碼沒有按照你的預期執行。若是你寫過 JS 的話,你可能知道上面的代碼爲何不會按預期運行。git
可是這個代碼確實作了你所指望的。github
async function logger() { let data = await fetch('http:sampleapi.com/posts') console.log(data) } logger()
直觀(和漂亮)的代碼可以正常運行,並且只添加了兩個關鍵字!api
在咱們深刻學習 async
和 await
以前,有必要先了解一下 promises 。要弄懂 promises,咱們須要再回到簡單的回調。promise
在ES6中引入了 Promises ,並對在 JavaScript 中編寫異步代碼作了很大的改進。再也不有所謂的 「回調地獄」,讓咱們對 Promises 產生了一點親切感。瀏覽器
回調是一個函數,能夠將結果傳遞給函數並在該函數內進行調用,以響應任何事件。 這是JS的基礎。less
function readFile('file.txt', (data) => { // 回調函數內部 console.log(data) }
這個函數只是簡單的從一個文件記錄數據,在文件完成以前進行讀取是不可能的。看起來很簡單,可是若是你想按順序讀取和記錄五個不一樣的文件怎麼辦?異步
在 Promises 出現以前,爲了執行順序任務,你須要嵌套回調,以下所示:async
// 這就是標準的回調地獄 function combineFiles(file1, file2, file3, printFileCallBack) { let newFileText = '' readFile(string1, (text) => { newFileText += text readFile(string2, (text) => { newFileText += text readFile(string3, (text) => { newFileText += text printFileCallBack(newFileText) } } } }
很難理解和跟蹤代碼。這還不包括可能出現的錯誤處理,好比其中一個文件不存在。
這時 Promise 就派上用場了。Promise 是對還沒有存在的數據進行推理的一種方法。你所不知道的 JavaScript 系列 的做者 Kyle Simpson 以異步 JavaScript 演講而聞名。他對 Promise 的 解釋 是:這就像是在快餐店裏點餐。
正如他指出的,當你在等餐的時候,你是不可能吃你的午飯,可是你能夠盼它,你能夠爲你的午飯作好準備。當你等餐的時候,你能夠進行其它事情,即便如今沒有拿到菜,可是這個午飯已經 「promise」 給你了。這就是所謂的 Promise。一個用於表示終將出現數據的對象。
readFile(file1) .then((file1-data) => { /* do something */ }) .then((previous-promise-data) => { /* do the next thing */ }) .catch( /* handle errors */ )
這是 Promise
的語法。它主要的優勢就是能夠將隊列事件以一種直觀的方式連接在一塊兒。雖然這個示例清晰易懂,可是仍是用到了回調。Promise 只是讓回調顯得比較簡單和更加直觀。
幾年前,async 函數被歸入了 JavaScript 生態系統。截止上個月,async 函數成爲了 JavaScript 語言的官方特性,並獲得了普遍支持。
async
和 await
關鍵字基於 pormise 和 generator 作了簡單的封裝。本質上,它容許咱們在所需的任意位置使用 await
關鍵字來「暫停」一個函數。
async function logger() { // 暫停直到獲取到返回數據 let data = await fetch('http://sampleapi.com/posts') console.log(data) }
這段代碼會按照你所指望的那樣運行。 它記錄了來自 API 調用的數據。若是你連這個都理解困難,那我也不知道咋辦了。
這樣作的好處是很是直觀。 你以大腦思考的方式編寫代碼,而後告訴代碼在所需的位置暫停。
另外一個好處就是可使用 promise 不能使用的 try
和 catch
:
async function logger () { try { let user_id = await fetch('/api/users/username') let posts = await fetch('/api/`${user_id}`') let object = JSON.parse(user.posts.toString()) console.log(posts) } catch (error) { console.error('Error:', error) } }
這是一個故意寫錯的例子,爲了證實了一點:catch
將捕獲在該過程當中的任何步驟發生的錯誤。至少有 3 個地方 try
可能會失敗,這是在異步代碼中的一種最乾淨的方式來處理錯誤。
咱們也可使用 async 函數讓循環和條件判斷再也不使人頭疼:
async function count() { let counter = 1 for (let i = 0; i < 100; i++) { counter += 1 console.log(counter) await sleep(1000) } }
這是一個愚蠢的例子,但這將會按照預期運行而且容易閱讀。 若是您在控制檯中運行此操做,你會看到代碼在調用 sleep 的時候暫停,下一個循環也不會等一秒鐘再啓動。
如今,你應該已經確信 async 和 await 的美妙之處,接下來咱們深刻了解一些細節:
async
和 await
基於 promise 的。 使用 async 的函數將會始終返回一個 promise 對象。 這一點很重要,要記住,這多是你遇到的容易犯錯的地。await
的時候咱們暫停了函數,而非整段代碼。async
和 await
是非阻塞的。Promise
例如 Promise.all()
。正如咱們以前的例子:async function logPosts () { try { let user_id = await fetch('/api/users/username') let post_ids = await fetch('/api/posts/${user_id}') let promises = post_ids.map(post_id => { return fetch('/api/posts/${post_id}') } let posts = await Promise.all(promises) console.log(posts) } catch (error) { console.error('Error:', error) } }
async
的函數。await
。// 拋出異常 function logger (callBack) { console.log(await callBack) } // 正常工做 async function logger () { console.log(await callBack) }
截至2017年6月,幾乎全部瀏覽器均可以使用 async
和 await
關鍵字。爲了確保你的代碼隨時可用,則須要使用 Babel 將你的 JavaScript 代碼編譯爲舊瀏覽器也支持的語法。