「这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战」
前言
async/await是一个语法糖,它和Promise、generator之间时息息相关的。所以在实现async/await之前先介绍一下它的用法和generator。
那什么是语法糖呢,语法糖就是用更简练的言语表达较复杂的含义。类似于汉语中的成语,我用四字成语能够表达一句话。所以语法糖就是用简单的方法来实现复杂的方法。
async/await 的用法
async/await 可以用同步的方法来执行异步操作,举个例子
function getData(nums) {
return new Promise(resolve => {
setTimeout(() => {
resolve(nums * 2)
}, 1000)
})
}
async function fn() {
const data = await getData(1)
console.log(data); // 1s 后打印出2
}
fn();
在getData方法执行完前,await后面的代码会被挂起,执行结束后才继续执行。如果await后也可以跟一个普通函数,会直接运行。
而使用async的方法,返回的是一个Promise
async function fn () {}
console.log(fn) // [AsyncFunction: fn]
console.log(fn()) // Promise {<fulfilled>: undefined}
小结一下。
- await只能在async函数中使用,不然会报错
- async函数返回的是一个Promise对象,有无值看有无return值
- await后面最好是接Promise,虽然接其他值也能达到排队效果
- async/await作用是用同步方式,执行异步操作
generator 的用法
generator即生成器,是ES6规范带来的新内容,在generator能够让我们在函数执行时任意地方暂停,在后续遇到合适的时机需要使用这个函数时继续执行。
是不是很眼熟,await 也可以让函数执行时的地方暂停,执行后再继续,所以 async/await其实是generator的语法糖。当然他们之间差别也很大,我们先来看看generator的使用方法。
function getData(num) {
return num
}
function* gen() {
const a = yield getData(1)
const b = yield getData(2)
const c = yield getData(3)
}
const g = gen() // 生成 iterator
console.log(g.next()) // { value: 1, done: false }
console.log(g.next()) // { value: 2, done: false }
console.log(g.next()) // { value: 3, done: false }
console.log(g.next()) // { value: undefined, done: true }
我们用*标明这是一个generator方法,里面使用yield来标明暂停点。generator方法不能自动执行,必须调用next方法,才会从一个暂停点走到下一个暂停点,直到运行结束,上面这个例子中,我们定义了三个暂停点,前三次执行时,done属性会返回false,value属性返回方法的return值。第四次next的done就变成了true,表面执行结束。
如果在使用next方法时传入参数,会当做yield语句的返回值。需要注意的是第一次next传参是没用的,只有从第二次开始next传参才有用,因为next传值时,他的顺序是,先右边yield,后左边接收参数。
所以下面的例子中,第二次next的传值会被赋值给a,也就是yield getData(1)的返回值;第三次next的传值赋值给b;第四次next的传值赋值给c。
function getData(num) {
return num
}
function* gen() {
const a = yield getData(1)
console.log('a:',a); // a: b
const b = yield getData(2)
console.log('b:',b); // b: c
const c = yield getData(3)
console.log('c:',c); // c: d
}
const g = gen() // 生成 iterator
console.log(g.next('a')) // { value: 1, done: false }
console.log(g.next('b')) // { value: 2, done: false }
console.log(g.next('c')) // { value: 3, done: false }
console.log(g.next('d')) // { value: undefined, done: true }
实现 async/await
前面说到 async/await 是 generator的语法糖。里面的yied 具有 await 类似的暂停功能,所以我们的实现可以使用generator函数。先分析他们之间的区别。
- generator函数执行返回值不是Promise,async执行返回值是Promise
- generator函数不支持自执行,需要执行相应的操作,才能等同于async的排队效果
- generator函数执行的操作是不完善的,因为并不确定有几个yield,不确定会嵌套几次 只要我们解决了这三个区别,也就实现了想要的效果。
首先,generator函数执行返回值不是Promise,所以我们需要封装一个高阶函数,接收一个generator函数,并经过一系列处理,返回一个具有async函数功能的函数。
function generatorToAsync(generatorFunc) {
return function() {
return new Promise((resolve, reject) => {
})
}
}
这样就解决了第一个区别。
第二个和第三个区别,generator函数不支持自执行,且不确定有几个yield,我们可以在上面的框架上执行一些操作。
function generatorToAsync(generatorFunc) {
return function() {
// 首先先调用generator函数 生成迭代器
const gen = generatorFunc.apply(this, arguments)
// 返回一个promise
return new Promise((resolve, reject) => {
// 内部定义一个step递归函数
function step(key, arg) {
let generatorResult
try {
// 执行next方法,或者报错后执行throw方法
generatorResult = gen[key](arg)
} catch (error) {
return reject(error)
}
const { value, done } = generatorResult
if (done) {
// 全部的yied执行结束后,调用 resolve
return resolve(value)
} else {
return Promise.resolve(value).then(val => step('next', val), err => step('throw', err))
}
}
step("next")
})
}
}
首先需要先执行generator函数来生成迭代器,然后在返回的Promise中定义一个递归方法step,一直运行next方法,直到返回值的done属性为true时终止,这样就能一个个调用 yied语句了。
我们和async/await对比一下。
// async/await
async function fn() {
const num1 = await getData(1);
console.log(num1); // 1s 后返回 2
const num2 = await getData(2);
console.log(num2); // 2s 后返回 4
const num3 = await getData(4);
console.log(num3); // 3s 后返回 8
}
const asyncRes = fn();
console.log(asyncRes); // Promise {<pending>}
asyncRes.then(res => {
console.log(10); // 3s 后返回 10
})
generator 版
function* gen() {
const num1 = yield getData(1)
console.log(num1) // 2
const num2 = yield getData(2)
console.log(num2) // 4
const num3 = yield getData(4)
console.log(num3) // 8
return 10
}
const asyncRes = generatorToAsync(gen)()
console.log(asyncRes); // Promise {<pending>}
asyncRes.then(res => {
console.log(10); // 3s 后返回 10
})
返回值和效果一摸一样。到这里我们就完成了 async/await 的实现。