node 初步 (三)

上一節中, 我們爲了優化異步的書寫, 寫出了生成器函數和promise的結合版本, 這看起來已經好極了
唯一的遺憾就是在需要每次書寫 run 函數 並在 run 函數中構建一個生成器函數
實際上, 瀏覽器已經爲此設計了一個專門的語法, 我們不再需要自己構建一個 run 函數了, 因爲在瀏覽器內部這個函數已經被實現好了, 格式如下:

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 時, 我們舉過加載圖片的例子

  • 串行加載, 串行顯示
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)

分析一下, 如果這麼寫的話, 效率可能並沒有原來高了, 因爲在 ① 處第一個文件夾加載完了之後才能加載第二個, 此時我們可以把它優化爲並行加載/串行顯示的方式:

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 還可能運行其他的程序, 但是還是有可以提升的地方

圖片描述

就像這樣,圖中黑色部分是等待時間, 藍色爲執行時間,

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章