async 和 await 是 Generator + Promise 的语法糖。async 和 await 中真正起作用的是 await。async 关键字,无论从哪方面看,都不过是一个标识符。毕竟异步函数如果不包含 await 关键字,其执行基本上跟普通函数没有区别。
await
通常使用 await 是后面会跟上一个表达式,这个表达式会返回一个 Promise。await 它可以放在任何异步的,基于 Promise 的函数之前。它会暂停代码在该行上,等到后面的 Promise 完成,然后返回结果值。不出结果之前,后面的代码不会执行。
注意 await 关键字只能放到 async 函数里面,现在写一个函数,让它返回 Promise 对象。该函数的作用是 2s 后让数值乘以 2。
function doubleAfter2Seconds(num) {
return new Promise((resolve,reject) => {
setTimeout(()=> {
resolve(2 * num)
}, 2000)
})
}
现在再写一个 async 函数,从而可以使用 await 关键字。await 后面放置的就是返回 Promise 对象的一个表达式,所以它后面可以写上 doubleAfter2Seconds 函数的调用。
async function testResult() {
let result = await doubleAfter2Seconds(30)
console.log(result)
}
// 现在调用testResult函数
testResult()
// 60 (2s之后)
现在我们看看代码的执行过程,调用 testResult 函数,它里面遇到了 await ,await 表示等一下。代码就暂停在这里,不再往下执行了,它在等什么?
等后面的 Promise 对象执行完毕,然后拿到 Promise resolve 的值并进行返回。返回值拿到后,它继续往下执行。遇到 await 代码暂停执行,等待 doubleAfter2Seconds 执行完毕。doubleAfter2Seconds 返回的 Promise 开始执行,2秒后 Promise resolve,并返回了值为60,这时 await 才拿到返回值60.然后赋值给 result,暂停结束,代码继续执行 console.log() 语句。
就这一个函数,我们可能看不出 async 和 await 的作用,如果我们要计算3个数的值,然后把得到的值进行输出呢?
async function testResult() {
let first = await doubleAfter2seconds(30)
let second = await doubleAfter2seconds(50)
let third = await doubleAfter2seconds(30)
console.log(first + second + third)
}
异步代码处理方式
暨 async 和 await 是如何实现的?
需求:连续发送三个请求,下一个请求将上一个请求的结果作为下一次请求的参数。
发起请求的代码:
function requestData(url) {
return new Promise((resolve, reject) {
// 模拟网络请求
setTimeout(function(){
resolve(url)
}, 2000)
})
}
方案一:请求处理函数中再发起请求
requestData('lxx').then(res => {
requestData(res + 'aaa').then(res => {
requestData(res + 'bbb').then(res => {
console.log(res) // lxxaaabbb
})
})
})
方案二:返回 Promise
requestData('lxx').then(res => {
return requestData(res + 'aaa')
}).then(res => {
return requestData(res + 'bbb')
}).then(res => {
console.log(res)
})
方案三:Promise + Generator
g.next().value 结果是一个 Promise,因为 requestData 返回一个 Promise then 方法中拿到 Promise 的结果 res,传入到下一个 next() 方法中。next() 方法的参数作为上一个 yield 的返回值如:res1,res1 即上一次 Promise 的结果。拿到上一个 yield 的返回值即上一次 Promise 的结果,就可以再次执行 yield。
// 生成器函数
function *getData() {
const res1 = yield requestData('lxx')
const res2 = yield requestData(res1 + 'aaa')
const res3 = yield requestData(res2 + 'bbb')
console.log(res3) // lxxaaabbb
}
// g 是生成器对象
const g = getData()
// 1. 手动执行生成器函数
g.next().value.then(res => {
g.next().value.then(res => {
g.next().value.then(res => {
console.log(res)
})
})
})
// 2. 生成器函数执行自动化
function execGeneratorFn(genFn) {
const generator = getFn()
function exec(res) {
const result = generator.next(res)
if(result.done) {
return result.value
}
result.value.then(res => {
exec(res)
})
}
exec()
}
// 自动执行生成器函数
execGeneratorFn(getData)
上面生成器函数自动化函数在 npm 里有 co 包,在 async 和 await 标准化之前,我们一般使用 co 来处理生成器函数的自动化执行。
// 安装co
npm i co
// 自动执行生成器函数
const co = require('co')
co(getData)
方案四:async 和 await
async function getData(url) {
let res1 = await requestData(url)
let res2 = await requestData(res1 + 'aaa')
let res3 = await requestData(res2 + 'bbb')
console.log(res3)
}
getData('lxx') // lxxaaabbb
async 函数返回值
异步函数始终返回期约对象。当这个 Promise 有了结果(resolve或reject)会进入对应的 .then 或 .catch 方法。.then 或 .catch 方法是异步的,会被加入到微任务队列。
返回普通值
如果 async 函数中有返回一个值,当调用该函数时,内部会调用 Promise.resolve() 方法把它转化成一个 Promise 对象作为返回值。
async function foo(flag) {
if(flag) {
return 'hello world'
} else {
throw 'my god, failure'
}
}
foo(true) // 调用 Promise.resolve() 返回 promise 对象
foo(false) // 调用 Promise.reject() 返回 promise 对象
返回 thenable 对象
thenable 中 resolve 的值会作为 async 函数返回的 Promise 的 resolve 值,并进入 .then 回调;thenable 中 reject 的值会作为 async 函数返回的 Promise 的 reject 值,并进入 .catch 回调。
async function foo() {
return {
then:function(resolve, reject) {
resolve('hello world')
// 或
reject('error')
}
}
}
const promise = foo()
promise.then(res => {
console.log(res) // hello world
}).catch(err => {
console.log(err) // error
})
返回 Promise
async 函数返回的 Promise 的 resolve 值进入 .then 回调,reject 值进入 .catch 回调。
async function foo() {
return new Promise((resolve, reject)=> {
setTimeout(()=> {
resolve('hello world')
// 或
reject('error')
}, 2000)
})
}
const promise = foo()
promise.then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
async 函数异常处理
异步函数中的异常,会作为异步函数返回的 Promise 的 reject 值,进入 .catch 回调。
async function foo() {
console.log('foo function start')
console.log('中间代码')
throw new Error('error message')
console.log('foo function end') // 不会执行
}
const promise = foo()
promise.catch(err => {
console.log(err.message) // error message
})
console.log('后续代码')
如果函数内部抛出错误,Promise 对象有一个 catch 方法进行捕获,如果没有捕获,程序会退出。