JS異步操做新體驗之 async函數

一、初識 async 函數
 
ES6中提供了兩個很好的解決異步操做的方案 Promise 和 Generator,ES2017標準中引入的 async 函數就是創建在 Promise 和 Generator的基礎之上,它是 Generator函數的語法糖,使異步操做更加方便
 
先經過一個異步讀取文件的小栗子來對比下Promise、Generator 和 async 的異同點
const fs = require('fs')

function readFile(fileName) {
  return new Promise((resolve, reject) => {
    fs.readFile(fileName, (err, data) => {
      if(err) {
        reject(err)
      }
      resolve(data.toString())
    })
  })
}

 

(1)、經過 Promise 讀取文件
readFile('data/a.txt').then(res => console.log(res))
                      .catch(err => console.log(err))

readFile('data/b.txt').then(res => console.log(res))
                      .catch(err => console.log(err))

readFile('data/c.txt').then(res => console.log(res))
                      .catch(err => console.log(err))

 

(2)、經過 Generator 函數讀取文件
與 Promise 相比較,優勢:把全部的接口都封裝在一個函數裏面了,缺點:代碼量稍微多一點點
function* gen() {
  yield readFile('data/a.txt')
  yield readFile('data/b.txt')
  yield readFile('data/c.txt')
}

let it = gen()

it.next().value.then(res => {
  console.log(res)
}).catch(err => {
  console.log(err)
})

it.next().value.then(res => {
  console.log(res)
}).catch(err => {
  console.log(err)
})

it.next().value.then(res => {
  console.log(res)
}).catch(err => {
  console.log(err)
})

 

 
(3)、經過 async 函數讀取文件
充分吸收了 Promise 和 Generator的優勢,同時避免了它們的缺點
async function read() {
  let readA = await readFile('data/a.txt')
  let readB = await readFile('data/b.txt')
  let readC = await readFile('data/c.txt')

  console.log(readA)
  console.log(readB)
  console.log(readC)
}

read()

 

最終的輸出結果express

 

經過上例能夠看出,async 函數就是將 Generator函數的星號("*")替換成了 async,把 yield 替換成了 await
 
async 函數對 Generator函數的改進,主要體如今三個方面:
 
(1)、內置執行器
async函數的執行,與普通函數如出一轍,只需一行;而 Generator函數,須要調用next 方法
 
(2)、返回值是 Promise
async函數的返回值是Promise,這比 Generator函數返回一個 Iterator對象方便多了
 
(3)、更好的語義化
從字面意思上來說,async是英文單詞 asynchronous 的縮寫,表示異步的;await中的 wait 是等待的意思。
 
所以相比 Generator函數中的星號和yield,語義更加清楚,async 表示這是一個異步操做的函數,await 表示緊跟在後面的表達式須要等待結果
 
 
二、async 函數的多種使用形式
// 函數聲明
async function foo() {
  // ....
}

// 函數表達式
let foo = async function() {
  // ....
}

// 箭頭函數
let foo = async() => {}

// 對象的方法
let obj = {
  name: 'Roger',
  async foo() {

  }
}
obj.foo().then(res => {
  // ....
})

// 類的方法
class Student{
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  async say() {
    return `My name is ${this.name}, I'm ${this.age} years old !`
  }
}

let jim = new Student('Jim Green', 13)
jim.say().then(res => console.log(res))   // My name is Jim Green, I'm 13 years old !

 

三、基本用法
 
async 函數返回一個 Promise 實例對象,可使用 then 方法添加回調函數。
 
當函數執行時,一旦遇到 await 就會先返回,等到異步操做完成,再接着執行函數體內後面的語句
 
// 休眠 ms 毫秒
function sleep(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms)
  })
}

async function print(ms) {
  console.log('start... ...')
  await sleep(ms)
  console.log('end... ...')
}

print(1000)

(1)、async 函數內部 return語句返回的值,會成爲then方法回調函數的參數
async function foo() {
  return 'hello world'
}

foo().then(res => console.log(res))   // hello world

 

(2)、async 函數內部拋出錯誤,會致使返回的 Promise對象變成reject狀態,拋出的錯誤會被catch方法回調函數接收到
async function bar() {
  return new Error('Error... ...')
}

bar().then(res => console.log(res))
     .catch(err => console.log(err))   // Error: Error... ...

 

(3)、只有 async 函數內部的異步操做執行完,纔會執行 then方法指定的回調函數
async function baz() {
  await new Promise(resolve => {
    console.log('執行第一個異步操做')
    setTimeout(resolve, 2000)
  })

  await new Promise(resolve => {
    console.log('執行第二個異步操做')
    setTimeout(resolve, 3000)
  })

  return '異步執行完畢再執行then方法'
}

baz().then(res => {console.log(res)})

四、await 命令
 
await 用於等待一個 Promise對象,它只能在一個 async函數中使用
[return_value] = await expression

表達式:一個 Promise對象或者任何要等待的值

返回值:返回 Promise對象的處理結果。若是等待的不是 Promise對象,則返回該值自己

 

await命令會暫停當前 async函數的執行,等待 Promise處理完成。若是 Promise正常處理,其回調的 resolve函數參數會做爲 await表達式的返回值,繼續執行 async函數。若是 Promise處理異常,await表達式會把 Promise的異常緣由拋出
 
// 若是 await 命令後的表達式的值不是一個 Promise,則返回該值自己
async function foo() {
  return await 123
}

foo().then(res => console.log(res))    // 123


// 若是 Promise 正常處理(fulfilled),其回調的resolve函數參數做爲 await 表達式的返回值
async function bar() {
  let f = await new Promise((resolve, reject) => {
    resolve('我是表達式的返回值')
  })
  console.log(f)   // 我是表達式的返回值

  return 'ending'
}

bar().then(res => {console.log(res)})    // ending


// 若是 Promise 處理異常(rejected),await 表達式會把 Promise 的異常緣由拋出
async function baz() {
  await new Promise((resolve, reject) => {
    reject(new Error('出錯啦......'))
  })
}

baz().then(res => console.log(res))
     .catch(err => console.log(err))     // Error: 出錯啦......

 

(1)、任何一個 await語句後面的 Promise對象變爲 reject狀態,那麼整個 async函數都會中斷執行
 
async function foo() {
  await Promise.reject('error')
  return 'ending'   // 未執行
}

foo().then(res => console.log(res))
// Uncaught (in promise) error

 

(2)、若是但願當前面的異步操做失敗時,不要中斷後面的異步操做,能夠把前面的 await放在try...catch結構裏面
 
async function foo() {
  try{
    await Promise.reject('error')
  } catch(e) {
    
  }
  return await Promise.resolve('執行完畢')
}

foo().then(res => console.log(res))    // 執行完畢

 

還能夠在 await後面的 Promise對象再跟一個 catch方法,處理前面可能出現的錯誤
 
async function foo() {
  await Promise.reject('error').catch(err => console.log(err))
  return '執行完畢'
}

foo().then(res => console.log(res))    // 執行完畢

 

(3)、若是想讓多個異步操做同時觸發,縮短程序的執行時間,能夠參考以下兩種寫法
 
// 寫法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 寫法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
相關文章
相關標籤/搜索