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;