前言
最近准备学习Koa,因此将JavaScript的异步操作又理了一遍,发现还是async await写起来比较直白,不愧是现在JavaScript异步操作的终极解决方案。
一、Async/Await的初识
Async/Await的含义
Async - 定义异步函数(async function someName(){...})
- 自动把函数转换为 Promise
- 当调用异步函数时,函数返回值会被 resolve 处理
- 异步函数内部可以使用 await
Await - 暂停异步函数的执行 (var result = await someAsyncCall())
- 当使用在 Promise 前面时,await 等待 Promise 完成,并返回 Promise 的结果
- await 只能和 Promise 一起使用,不能和 callback 一起使用
- await 只能用在 async 函数中
Async/Await 和 Generator
async 函数是ES6所提出的,本质上是Generator函数的语法糖,但它在以下四点做了长足的改进:
- 内置执行器:Generator 函数的执行必须依靠执行器,而 async 函数则自带执行器,调用的方式和普通函数调用一样。
- 语义化:较之于Generator 函数的 * 和 yield,async/await无疑是更为语义化。
- 返回的值是Promise: async 函数返回值是Promise对象,及哦啊哈子与Generator 函数所返回的Lterator对象更加方便,可以直接使用 then() 方法进行链式调用。
- 适用范围更广: co 模块约定,yield 命令后面只能是 Thunk 函数或 Promise对象。而 async 函数的 await 命令后面则可以是 Promise 或者 原始类型的值。
二、async 语法
首先 async 函数返回一个 Promise 对象,也就是说 async 函数内部 return 返回的值,会成为 then 方法回调函数的参数,等同于 return Promise.resolve(value)。
async function f() {
return 'hello async'
};
f().then( (v) => console.log(v))
// hello async
其次 async 函数所返回的 Promise 对象,必须等到内部所有的 await 命令的 Promise 对象执行完成后,才会发生状态改变。
const delay = timeout => new Promise(resolve=> setTimeout(resolve, console.log(timeout) timeout))
async function f(){
await delay(1000)
await delay(2000)
await delay(3000)
return 'end'
}
f().then(v => console.log(v)) // 需要等待6秒后才会输出"end"
正常情况下,await 命令后面跟着的是 Promise ,如果不是的话,也会被转换成一个 立即 resolve 的 Promise。
async function f() {
return await 1
};
f().then( (v) => console.log(v)) // 1
最后如果 async 函数内部抛出异常,则会导致返回的 Promise 对象状态变为 reject 状态。抛出的错误而会被 catch 方法回调函数接收到。
async function e(){
throw new Error('error');
}
e().then(v => console.log(v))
.catch( e => console.log(e));
三、async和其他异步操作的比较
直接上代码:
首先定义一个可以获取 github user 的 fetch 方法:
function fetchUser() {
return new Promise((resolve, reject) => {
fetch('https://api.github.com/users/srtian')
.then((data) => {
resolve(data.json())
}, (error) => {
reject(error)
})
})
}
Promise
function getUserByPromise() {
fetchUser()
.then((data) => {
console.log(data)
}, (error) => {
console.log(error)
}
}
getUserByPromise();
这样看起来使用 Promise 来进行异步操作好像非常不错,但有个问题是,一旦then变多了,代码将会变得非常冗长和复杂,且语义化不明显,代码流程不能很好的表示执行的流程。
Generator
function *fetchUserByGenerator() {
const user = yield fetchUser()
return user
}
const g = fetchUserByGenerator()
const result = g.next().value
result.then((v) => {
console.log(v)
}, (error) => {
console.log(error)
})
Generator 的方式解决了 Promise 的一些问题,流程更加直观、语义化。但是 Generator 的问题在于,函数的执行需要依靠执行器,每次都需要通过 g.next() 的方式去执行。而且虽然语义上有所进步,但 * 和 yield 明显还是不能满足我们对语义化直观观察的需要。
async
async function getUserByAsync(){
let user = await fetchUser()
return user
}
getUserByAsync()
.then(v => console.log(v))
而async/await则很好的解决了上面两种异步操作的一些问题。首先使用同步的方法来写异步,代码非常清晰直观;其次使用async和await,在语义上非常好,一眼就能看出代码执行的顺序;最后 asunc 函数自带执行器,执行的时候无需手动加载。
四、其他
错误处理
除了上面的那些东西,Async 函数的错误处理也需要额外注意:
let a
async function f() {
await Promise.reject('error')
a = await 1
}
f().then(v => console.log(a))
上面的 a = await 1 没有执行,这是由于在async函数中,只要有一个await出现 reject 状态,那么后面的await都不会被执行。所以我们就需要使用try/catch来解决这个问题:
let a
async function correct() {
try {
await Promise.reject('error')
} catch (error) {
console.log(error)
}
a = await 1
return a
}
correct().then(v => console.log(a)) // 1
并行
当我们需要使用并行进行加载时,使用async可能可以实现,但这是很低效的比如这样:
await a()
await b()
await c()
await d()
换成回调就是这样的:
a(() => {
b(() => {
c(() => {
d()
})
})
})
然而我们发现,原始代码中,函数 c 可以与 a 同时执行,但 async/await 语法会让我们倾向于在 b 执行完后,再执行 c。
所以我们其实可以这样:
async function ab() {
await a()
b()
}
async function cd() {
await c()
d()
}
Promise.all([ab(), cd()])
参考资料:
https://segmentfault.com/a/1190000014753495
https://juejin.im/post/596e142d5188254b532ce2da