业精于勤 荒于嬉
async await 是什么?
async 函数是对Generator 函数的改进,也就是generator 函数的语法糖,它建立在Promise 上,并且与所有现有的基于Promise 的 API 兼容。
1、async 声明一个异步函数(async function someName(){....})
2、自动将常规函数转换为Promise,返回值也是一个Promise对象
3、只有 async 函数 内部的异步操作执行完,才执行 then 方法制定的回调函数
4、异步操作内部可以使用 await
①、await ..... 暂停异步操作的功能执行 (let result = await someAsyncCall())
②、防止在 Promise 调用之前,await 强制其他代码等待,直到 Promise 完成并返回结果
③、只能与Promise 一起使用,不适用于回调
④、只能在 async 函数内部使用
理解实践
由于async 函数返回的是 Promise 对象,可以作为 await 命令的参数
function timeout(ms) {
return new Promise((resolve => {
// setTimeout 函数 代表的就是 ms 时间之后执行 resolve
setTimeout(resolve, ms)
}))
}
async function asyncPrint(value, ms) {
await timeout(ms)
console.log(value)
}
asyncPrint('都是套路', 50).then(() => {
console.log("测试一下")
})
async 函数内部的return 语句 返回值 会成为 then 方法回调函数的参数
async function f() {
return 'hello World'
}
f().then(v => console.log(v))
async 函数内部抛出错误,会导致返回的Promise 对象变为 reject 状态,抛出的错误对象会被catch 方法回调函数接收到
async function ff() {
throw new Error('脑子有问题了')
}
ff().then(v => console.log("resolve", v), e => console.log('reject', e)).catch(err => console.log('catch::err',
err))
ff().then(v => console.log("resolve", v)).catch(err => console.log('catch::err',
err))
Promise 对象的状态变化:async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
async function getTitle(url) {
let response = await fetch(url)
let html = await response.text();
return html.match(/<title>([\s\S]+)<\/title>/i)[1]
}
getTitle('https://tc39').then(r => console.log('r::', r)).catch(err => console.log('err::', err))
// await 命令
function sleep(interval) {
return new Promise(resolve => {
setTimeout(resolve, interval)
})
}
// 使用
async function one2fiveInAsync() {
for (let i = 1; i <= 5; i++) {
console.log(i)
await sleep(1000)
}
return 'haha'
}
one2fiveInAsync().then(val => {
console.log('one2fiveInAsync::', val)
})
// await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到
async function fff() {
await Promise.reject('出错了')
}
fff()
.then(v => console.log(v))
.catch(e => console.log(e))
// 任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。
async function ffff() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}
有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。
async function f2() {
try {
await Promise.reject('出错了')
} catch (e) {
console.log("ee::", e)
}
return await Promise.resolve('hello world f2')
}
f2().then(v => console.log(v))
// 另一种方法是await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误。
async function f3() {
await Promise.reject('f3 出错了').catch(e => {
console.log(e)
});
return await Promise.resolve('hello world f3')
}
f3().then(v => console.log(v))
如果有多个await命令,可以统一放在try...catch结构中。
function getFoo() {
console.log('getFoo')
}
function getBar() {
console.log('getBar')
}
async function f4() {
// 多个await 命令后面的异步操作,如果不存在继发关系,最好让他们
// 同事触发
// let foo1 = await getFoo()
// let bar1 = await getBar()
// 像以上代码 getFoo 和 getBar 是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo 完成以后,才会执行getBar,完全可以让它们同时触发。
// // 写法一
// let [foo2, bar2] = await Promise.all([getFoo(), getBar()])
// // 写法二
let fooPromise = getFoo()
let barPromise = getBar()
let foo3 = await fooPromise;
let bar3 = await barPromise;
}
f4()
await 只能用在async 函数 ,但是如果将forEach方法的参数 改成 async 函数,也有问题
function dbFuc(db) {
let docs = [{}, {}, {}];
//可能得到错误结果
docs.forEach(async function (doc) {
await db.post(doc)
})
}
// 上面代码可能不会正常工作,原因是这时三个db.post()操作将是并发执行
// 而不是继发执行 正确的写法应该采用for 循环
async function dbFun(db) {
let docs = [{}, {}, {}];
for (let doc of docs) {
await db.post(doc)
}
}
async/await 相比于 Promise 的 优势
- 代码读起来更加同步,Promise虽然摆脱了回调地狱,但是then的链式调⽤也会带来额外的阅读负担
- Promise传递中间值非常麻烦,而async/await几乎是同步的写法,非常优雅
- 错误处理友好,async/await可以使用成熟的try/catch,Promise的错误捕获非常冗余
- 调试友好,Promise的调试很差,由于没有代码块,你不能在⼀个返回表达式的箭头函数中设置断点,如果你在一个.then代码块中使⽤调试器的步进(step-over)功能,调试器并不会进入后续的.then代码块,因为调试器只能跟踪同步代码的『每一步』。