ES2017 中的 Async 和 Await

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

ES6 以前的異步 JavaScript

在咱們深刻學習 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 就派上用場了。Promise 是對還沒有存在的數據進行推理的一種方法。你所不知道的 JavaScript 系列 的做者 Kyle Simpson 以異步 JavaScript 演講而聞名。他對 Promise 的 解釋 是:這就像是在快餐店裏點餐。

  1. 點餐。
  2. 付錢並得到取餐小票。
  3. 等餐。
  4. 當餐準備好了,他們會叫你的單號提醒你取餐。
  5. 取餐。

正如他指出的,當你在等餐的時候,你是不可能吃你的午飯,可是你能夠盼它,你能夠爲你的午飯作好準備。當你等餐的時候,你能夠進行其它事情,即便如今沒有拿到菜,可是這個午飯已經 「promise」 給你了。這就是所謂的 Promise。一個用於表示終將出現數據的對象。

readFile(file1) .then((file1-data) => { /* do something */ }) .then((previous-promise-data) => { /* do the next thing */ }) .catch( /* handle errors */ )

這是 Promise 的語法。它主要的優勢就是能夠將隊列事件以一種直觀的方式連接在一塊兒。雖然這個示例清晰易懂,可是仍是用到了回調。Promise 只是讓回調顯得比較簡單和更加直觀。

最佳(且最新)方式: Async / Await

幾年前,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) } }
  • await 只能用於被聲明爲 async 的函數。
  • 所以,不能在全局範圍內使用 await
// 拋出異常 function logger (callBack) { console.log(await callBack) }   // 正常工做 async function logger () { console.log(await callBack) }

如今就能夠用啦

截至2017年6月,幾乎全部瀏覽器均可以使用 async 和 await 關鍵字。爲了確保你的代碼隨時可用,則須要使用 Babel 將你的 JavaScript 代碼編譯爲舊瀏覽器也支持的語法。

相關文章
相關標籤/搜索