(一)async/await 使用了 Generator 和 Promise 两种技术
1. Generator 生成器函数
生成器函数是一个带星号函数,而且是可以暂停执行和恢复执行的函数。
(1)在生成器函数内部执行一段代码,如果遇到 yield 关键字,那么 JavaScript 引擎将返回关键字后面的内容给外部,并暂停该函数的执行。
(2)外部函数可以通过 next 方法恢复函数的执行。
要搞懂函数为何能暂停和恢复,那你首先要了解协程的概念。协程是一种比线程更加轻量级的存在。可以把协程看成是跑在线程上的任务,一个线程上可以存在多个协程,但是在线程上同时只能执行一个协程,比如当前执行的是 A 协程,要启动 B 协程,那么 A 协程就需要将主线程的控制权交给 B 协程,这就体现在 A 协程暂停执行,B 协程恢复执行;同样,也可以从 B 协程中启动 A 协程。通常,如果从 A 协程启动 B 协程,我们就把 A 协程称为 B 协程的父协程。
例子:
// 声明一个构造器函数
function* generator () {
console.log("开始执行第一段")
yield 1
console.log("开始执行第二段")
yield {name: 'ss'}
console.log("开始执行第三段")
yield 3
console.log("执行结束")
return 4
}
// 声明一个迭代器
let gen = generator()
// 执行第一步
console.log(gen.next())
返回一个对象,value 的值是 yield 后的值,done 表示该迭代器是否执行到最后一步
// 执行第二步
console.log(gen.next())
// 执行第三步
console.log(gen.next())
// 执行第四步
console.log(gen.next())
此时的done 变为true ,表示该迭代器执行完毕。
如果再next
console.log(gen.next())
则value 返回 undefined,done 为 true
注意:如果 yield 后没有值 ,value也是undefined
2. 消息传递
yield... 和 next(...) 这一对组合起来,在生成器的执行过程中构成了一个双向消息传递系统。
next()的参数是传给生成器的,可以用来调节生成器的行为。而yield的值是返回给next()的调用者的。两者的区别如下:
next()参数 : 调用者 ---> 生成器
yield 表达式:生成器 ---> 调用者
next()
方法带一个参数,该参数就会被当作上一个yield
表达式的返回值。例子如下:
function* main(x) {
const y = x * (yield 12)
return y
}
const it = main(6) // step1
console.log(it.next()) // step 2
console.log(it.next(7)) // step3
step1: 向生成器中传入参数 6 ,则 向赋值为 6
step2: value 为 yield 后的值 value 为 12
step3: next() 向生成器中传入值 7,所以(yield 12)整体被看作 7 ,y 为 42
结果运行如下:
说明:
(1)第一个 next 是启动生成器的,没有上一个yield,所以第一个next传参无效,该参数会被忽略。
(2)如果不带参数,则表示上一个参数返回undefined
3. Generator、Promise 实现完美异步
当请求一个接口时:
<script>
function getData () {
return fetch('https://www.fastmock.site/mock/e9463cacabbb7690593cdbaf53f1480d/clean/test/a')
}
function* dataCallBack() {
try {
let result = yield getData()
} catch(err) {
console.log(err)
}
}
let it = dataCallBack()
// {value: Promise, done: false}
let promise = it.next().value
promise.then((res) => {console.log(res)})
</script>
当请求多个接口时:
<script>
// generator + Promise 实现 await/async
// 1. Promise
function tick (time) {
return new Promise ((resolve, reject) => {
setTimeout(() => {
resolve('当前时间:' + new Date())
}, time)
})
}
// 2. generator
function* gene() {
let result1 = yield tick(3000)
console.log('result1', result1)
let result2 = yield tick(2000)
console.log('result2', result2)
}
// 驱动函数
let run = (generator, res) => {
let start = generator.next(res)
if (start.done) return
start.value.then((res) => {
run(generator, res) // 回调
})
}
// 执行
run(gene())
(二)async 是什么
根据 MDN 定义,async 是一个通过异步执行并隐式返回 Promise 作为结果的函数。
<script>
async function a () {
return 3
}
console.log(a()) // Promise {<resolved>: 3}
</script>
async 返回一个Promise
async function foo() {
console.log(1)
let a = await 100
console.log(a)
console.log(2)
}
console.log(0)
foo()
console.log(3)
执行结果:0 1 3 100 2
知识点:Promise 立即执行 1 输出在3 前面
总结:Promise 的编程模型依然充斥着大量的 then 方法,虽然解决了回调地狱的问题,但是在语义方面依然存在缺陷,代码中充斥着大量的 then 函数,这就是 async/await 出现的原因。
1. generator 函数是如何暂停执行程序的?
答案是通过协程来控制程序执行。
generator 函数是一个生成器,执行它会返回一个迭代器,这个迭代器同时也是一个协程。一个线程中可以有多个协程,但是同时只能有一个协程在执行。线程的执行是在内核态,是由操作系统来控制;协程的执行是在用户态,是完全由程序来进行控制,通过调用生成器的next()方法可以让该协程执行,通过yield关键字可以让该协程暂停,交出主线程控制权,通过return 关键字可以让该协程结束。协程切换是在用户态执行,而线程切换时需要从用户态切换到内核态,在内核态进行调度,协程相对于线程来说更加轻量、高效。
2. async function实现原理?
async function 是通过 promise + generator 来实现的。generator 是通过协程来控制程序调度的。
在协程中执行异步任务时,先用promise封装该异步任务,如果异步任务完成,会将其结果放入微任务队列中,然后通过yield 让出主线程执行权,继续执行主线程js,主线程js执行完毕后,会去扫描微任务队列,如果有任务则取出任务进行执行,这时通过调用迭代器的next(result)方法,并传入任务执行结果result,将主线程执行权转交给该协程继续执行,并且将result赋值给yield 表达式左边的变量,从而以同步的方式实现了异步编程。
所以说到底async function 还是通过协程+微任务+浏览器事件循环机制来实现的。
参考文章:https://www.jianshu.com/p/83da0901166f