了解 async/await 更优雅的异步处理的解决方案
async/await 是基于 Promise 的进一步的一种优化,处理方式更加优雅。
从字面意思上理解async/await: async 是异步的意识,await 有等待的意思,而两者的用法上也是如此。async用于声明一个异步的 function,而 await 用于等待一个异步方法执行完成。
- async/await 是写异步代码的新方法,以前的方式又回调函数和Promise
- async/await 是基于 Promise 实现的,不能用于普通的回调函数
- async/await 与 Promise 一样, 是非阻塞的
- async/await 是的异步代码看起来像同步代码,这是它的魔力所在
// Promise 示例
function getUserInfo() {
ajaxGetUserInf()
.then(data => {
return data
})
}
// async await 基础示例
async function getUserInfo() {
let result = await ajaxGetUserInf();
console.log(result); // 异步请求的结果
return result
}
基本规则
- async 表示这是一个 异步函数, await 只能用在这个函数里面
- await 表示在这里等待 Promise,返回结果了,再继续执行
- await 后面跟着的应该是一个 Promise 对象,当然,其他返回值也没有关系,只是会立即执行,不过这样就没有意义了。
async 关键字
// async 示例
async function demoFn() {
return 100;
}
demoFn().then((res) => {
console.log(res); // 100
})
# async 函数会返回一个 promise 对象,如果函数中返回的是一个值,async直接会用Promise.resolve()包裹一下返回
- 表明程序里面可能有异步过程: 里面可以有await关键字;当然全部是同步代码也没有关系,但是这样async关键字就显得多余了;
- 非阻塞:async函数里面如果有异步过程会等待,但是async函数本身会马上返回,不会阻塞当前线程,可以简单认为,async函数工作在主线程,同步执行,不会阻塞界面渲染,async函数内部由await关键字修饰的异步过程,工作在相应的协程上,会阻塞等待异步任务的完成再返回。
- async函数返回类型为Promise对象:这是和普通函数本质上的不同,也是使用时重点注意的地方;
- return new Promise() 这个符合async函数本意
- return data 这个是同步函数的写法,这里是要特别注意的,这个时候,其实就相当于Promise.resolve(data) 还是一个Promise对象,但是在调用async函数的地方通过简单的调用是拿不到这个data的。因为返回值是一个Promise对象,所以需要使用 .then(data => {}) 的方式才可以拿到这个data
- 如果没有返回值,相当于返回了 Promise.resolve(undefined)
- 无等待:联想到Promise的特点,在没有await的情况下执行async函数,会立即执行,返回一个Promise对象,并且绝对不会阻塞后面的语句,这和普通返回Promise对象的函数并无二致
- await不处理异步error: await是不管异步过程的reject(error)消息的,async函数返回的这个Promise对象的catch函数负责统一抓取内部所有异步过程的错误;async函数内部只要有一个异步发生过错误,整个执行过程就中断,这个返回的Promise对象的catch就能抓取到这个错误;
- async函数的执行: async函数执行和普通函数一样,参数个数随意,没有限制,也需要有async关键字;只是返回值是一个Promise对象,可以用then函数得到返回值,用catch抓取整个流程中发生的错误;
await 关键字
- await只能在async函数内部使用: 不能放在普通函数里面,否则会报错
- await关键字后面跟Promise对象: 在Pending状态时,相应的协程会交出控制权,进入等待状态,这是协程的本质
- await是async await的意识: await的是resolve(data)的消息,并把数据data返回,比如下面代码中,当Promise对象由Pending变成Resolved的时候,变量a就等于data,然后再顺序执行下面的语句console.log(a),这真的是等待,真的是顺序执行,表现和同步代码几乎一模一样
const a = await new Promise((resolve, reject) => {
return resolve(100)
})
console.log(a)
- **await后面也可以跟同步代码: ** 不过系统会提示将其转换成一个Promise对象,比如:
const a = await 'hello world!'
// 相当于
const a = await Promise.resolve('hello world!')
// 跟同步代码是一样的,还不如省事一点,直接去掉await关键字
const a = 'hello world!'
- await对于失败消息的处理: await只关心异步过程成功的消息resolve(data),拿到相应的数据data,至于失败消息reject(error),不关心不处理;对于错误的处理由以下几种方法供选择:
- 让await后面的Promise对象自己catch
- 也可以让外面的async函数返回的Promise对象统一catch
- 像同步代码一样,放在一个try..catch结构中
async function mount() {
// 将异步和同步的代码放在一个try...catch中,异常都能抓到
try {
let array = null;
let data = await asyncFn();
if(array.length > 0) { // 抛出异常,在catch中打印异常
array.push(data)
}
} catch (error) {
console.log(JSON.stringify(error))
}
}
- **await对于结果的处理: ** await是个运算符,用于组成表达式,await表达式的运算结果取决于它等的东西,如果它等到的不是一个Promise对象,那么await表达式的运算结果就是它等到的东西,如果是Promise对象,await就忙起来了,会阻塞后面的代码,等着Promise对象resolve,然后得到resolve的值,作为await表达式的运行结果;虽然是阻塞,但async函数调用并不会造成阻塞,它内部所有的阻塞都被封装在一个Promise对象中异步执行,这也正是await必须用在async函数中的原因
用 async/await 替换 Promise 的不完美 ?
- 错误处理示例
// 定义getJSON
function getJSON() {
return Promise.resolve("ok")
}
# 在以下的Promise示例中,try/catch不能处理JSON。parse的错误,因为它在Promise中。我们需要使用.catch这样错误处理代码非常冗余。并且,在实际生产代码会更加复杂。
function makeRequest() {
try {
getJSON().then(result => {
// JSON.parse 会报错
const data = JSON.parse(result)
console.log(data)
})
// 取消注释,处理异步代码的错误
//.catch((err) => {
// console.log(err)
//})
} catch (error) {
console.log(error)
}
}
# 使用 async/await 让 try/catch 可以同事处理同步和异步错误。能处理 JSON.parse 错误; async/await 最让人舒服的一点是代码看起来是同步的
async function makeRequest() {
try {
const data = JSON.parse(await getJSON())
console.log(data)
} catch (error) {
console.log(error)
}
}
- 条件语句示例
# 根据返回数据决定是直接返回,还是继续获取更多的数据
function makeRequest() {
return getJSON()
.then(data => {
if(data.need) {
return makeNeed(data)
.then(moreData => {
console.log(moreData)
return moreData
})
} else {
console.log(data)
return data
}
})
}
// 注意:这种多层嵌套并且 return语句很容易让人感到迷茫,而它们只是需要将最终结果传递到最外层的Promise
# 使用 async/await 这才是真正摆脱回调地狱的正确做法
async function makeRequest() {
const data = await getJSON()
if (data.need) {
const moreData = await makeNeed(data)
console.log(moreData)
return moreData
} else {
console.log(data)
return data
}
}
- 中间值(根据上一次的结果进行下一次调用) -链式调用
# 场景分析: 调用 promise1,使用 promise1 返回的结果去调用 promise2,然后使用两者的结果去调用 promise3
function makeRequest() {
return promise1()
.then(value1 => {
return promise2(value1)
.then(value2 => {
return promise3(value1, value2)
})
})
}
// 或者
function makeRequest() {
return promise1()
.then(value1 => {
return Promise.all([value1, promise2(value1)])
})
.then(([value1, value2]) => {
return promise3(value1, value2)
})
}
// 无论怎么写都会觉得复杂,使用async/await,将复杂的场景简化
async function makeRequest() {
const value1 = await promise1()
const value2 = await promise2(value1)
return promise3(value1, value2)
}