最近突然发现一个问题,起因是在面试的时候,有一道基础知识题,答对的寥寥无几,题目如下:
async function async1() {
console.log('async1')
await async2()
console.log('async2')
}
async function async2() {
console.log('async2')
}
async1()
new Promise((resolve, reject) => {
console.log('promise1')
resolve()
}).then(res => {
console.log('promise2')
})
这题原本不难,但是一旦 async/await 与 promise 混合在一起,错误率飙升,这主要还是没有很好的理解 async / await 机制。
理解这题之前,首先要熟悉事件循环(Event Loop),对于事件循环中的宏任务与微任务,推荐看这个网址:jakearchibald.com/2015/tasks-…,内部有动画一步步执行入栈和出栈的过程。
对于 async / await 的理解,首先得理解函数前面加上 async 标记,函数会有什么变化:
async function asynctest() {
let result = false
console.log(result, 'result')
return result
}
function test() {
let result = false
console.log(result, 'result')
return result
}
根据上图所得,函数前面加上 async 标记,那么返回值会被包裹一层 Promise 封装。
如果不用 async 标记,如果实现同样的效果呢:
function test() {
let result = false
console.log(result, 'result')
return Promise.resolve(result)
}
// PS
Promise.resolve(false) === new Promise(res => res(false))
那么我们再加上 await 会不会有什么变化:
async function asynctest() {
let result = await false
console.log(result, 'result')
return result
}
这看起来与没有加 await 没有区别,那把 await 后面改成异步函数再看看:
function syncFn() {
return new Promise((res) => {
setTimeout(() => {
res('syncFn')
}, 1000)
})
}
async function asynctest() {
let result = await syncFn()
console.log(result, 'await result')
return result
}
以上结果可知:函数前面加了 async 标记,那么函数的返回结果会被包裹一层 Promise,而 await 可以加在一个表达式的前面,阻塞当前函数执行栈,直到表达式执行完成。await 等待只是拿到表达式返回的值。因此,如果把 async 函数改写成 Promise 的风格的写法,可以写成这样。
async function asynctest() {
let result = await syncFn()
console.log(result, 'await result')
return result
}
// 改写成
function asynctest() {
let result = new Promise(res => {
let ret = syncFn()
res(ret)
}).then(res => {
console.log(result, 'await result')
return res
})
return result
}
这样只用 Promise 改写,在某些场景并不好理解,比如:
function syncFn() {
return new Promise((res) => {
setTimeout(() => {
res('syncFn')
}, 1000)
})
}
async function test(){
for (let i = 0; i < 10; i++){
let res = await syncFn()
console.log(res, i)
}
}
await 是可以阻塞 for 循环的,如果用 promise 改写,这又得写成地狱回调方法:
function syncFn() {
return new Promise((res) => {
setTimeout(() => {
res('syncFn')
}, 1000)
})
}
function asynctest(i) {
if (i > 9) return
let result = new Promise(res => {
let ret = syncFn()
res(ret)
}).then(res => {
console.log(res, i)
asynctest(i + 1)
return res
})
}
对于阻塞函数的场景,应该很容易想到 generate 函数:
function *generate() {
for (let i = 0; i < 5; i++) {
let res = yield i
console.log(res)
}
console.log('end')
}
所以可以结合 generate 函数与 promise:
写一个自执行的 generator 函数:
// generator 自执行器
function asyncToGenerator(genFn) {
return new Promise((resolve, reject) => {
let gen = genFn()
function step(key, arg) {
let info = {}
try {
info = gen[key](arg);
} catch (error) {
reject(error)
return
}
if (info.done) {
resolve(info.value)
} else {
return Promise.resolve(info.value).then(v => {
return step('next', v)
}, (error) => {
return step('throw', error)
})
}
}
step('next')
})
}
然后测试一下:
let test = function () {
let ret = asyncToGenerator(function* () {
for (let i = 0; i < 10; i++) {
let result = yield syncFn();
console.log(result, i);
}
})
return ret
}