1、初識 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()
最終的輸出結果
通過上例可以看出,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 表示緊跟在后面的表達式需要等待結果
2、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 !
3、基本用法
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)})
4、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;