上一節中, 咱們爲了優化異步的書寫, 寫出了生成器函數和promise的結合版本, 這看起來已經好極了
惟一的遺憾就是在須要每次書寫 run 函數並在 run 函數中構建一個生成器函數
實際上, 瀏覽器已經爲此設計了一個專門的語法, 咱們再也不須要本身構建一個 run 函數了, 由於在瀏覽器內部這個函數已經被實現好了, 格式以下:promise
function squareAsync(x) { return new Promise(resolve => { setTimeout(() => resolve(x * x), 1000 + Math.random() * 2000) }) } async function foo() { // 這句對應 function * foo() var a = await squareAsync(5) // await 對應 yield console.log(a) return Promise.reject(3) } // > Promise {<pending>} // 25 // 3 foo().catch(val => console.log(val))
這裏的async function foo() 就像瀏覽器在run中運行 foo, 此時也更方便給 foo 函數傳參, 一樣 await 後接 promise, 函數運行中遇到 await 就會暫停, 直到等到 promise 的運行結果再繼續執行, 這裏的 promise 再也不須要調用 then 來獲得求值結果, 直接就至關於一條賦值語句。 async 並非一個關鍵字, 必須和function一塊兒纔有效果
注意,這裏函數的返回結果是一個promise, 因此能夠在 foo 後調用 then/catch 來接收 foo 的返回結果瀏覽器
在前面學習 Promise 時, 咱們舉過加載圖片的例子dom
async function showStory(storyUrl) { var story = await getJSON(storyUrl) for (var chapterUrl of story.chapterUrls) { var chapter = await getJSON(chapterUrl)//一張一張加載 addContentToPage(chapter) } }
async function showStory(storyUrl) { var story = await getJSON(storyUrl) var chapterPromises = story.chapterUrls.map(getJSON)// 同時加載 for (var chapterPromise of chapterPromises) { var chapter = await chapterPromise addContentToPage(chapter) } }
再來寫一下上一節讀取文件名的第 4 個版本, async版本的函數異步
const fs = require('fs') const fsp = fs.promises async function listAllFiles(path) { var result = [] var stat = await fsp.stat(path) if (stat.isFile()) { return [path] } else { var entries = await fsp.readdir(path, { withFileTypes: true }) for (let entry of entries) { // 這裏不能用forEach. 由於forEach不能接異步函數 var fullPath = path + '/' + entry.name var files = await listAllFiles(fullPath) // ① result.push(...files) } return result } } listAllFiles('.').then(console.log)
分析一下, 若是這麼寫的話, 效率可能並無原來高了, 由於在 ① 處第一個文件夾加載完了以後才能加載第二個, 此時咱們能夠把它優化爲並行加載/串行顯示的方式:async
const fs = require('fs') const fsp = fs.promises async function listAllFiles(path) { var result = [] var stat = await fsp.stat(path) if (stat.isFile()) { return [path] } else { var entries = await fsp.readdir(path, { withFileTypes: true }) var entryPromises = entries.map(entry => { var fullPath = path + '/' + entry.name return listAllFiles(fullPath) }) for (let entryPromise of entryPromises ) { var files = await entryPromise result.push(...files) } return result } } listAllFiles('.').then(console.log)
這種方法纔是性能更高的一個, 同時在等待的時候 cpu 還可能運行其餘的程序, 可是仍是有能夠提高的地方, 由於此方法爲串行等待每一個任務, 即使第二個完成了第一個沒完成, 也不能處理第二個函數
就像這樣,圖中黑色部分是等待時間, 藍色爲執行時間, 如: 最長等待時間爲10s , 執行時間爲1s, ①一共須要15s , 而②只須要11s的時間, 由於這裏用了for循環, 一樣這裏使用 promise all 也不行, 因此這裏最好的方法反而是回調函數性能
const fs = require('fs') const fsp = fs.promises async function listAllFiles(path) { var result = [] var stat = await fsp.stat(path) if (stat.isFile()) { return [path] } else { var entries = await fsp.readdir(path, { withFileTypes: true }) var entryPromises = entries.map((entry,i) => { var fullPath = path + '/' + entry.name return listAllFiles(fullPath).then(files => { result[i] = files }) }) var entryValues = await Promise.all(entryPromises) return [].concat(...result) } } listAllFiles('.').then(console.log)
這樣就達到了上圖中②的效果, 即全部任務同時開發學習