这次10分钟就帮你搞定async/await原理!

Javascript 虽博大精深,但如果只能同步运行,那么遇到 HTTP 网络请求、I/O 处理、事件、定时器等耗时操作就会卡到没法用。那么 js 是如何解决异步编程的?
回调函数 在 js 中常常将函数作为参数传递,当任务执行有了结果就调用这个函数来进行下一步操作。假设我们需要拿到前一个请求的结果来进行下一个请求,一旦请求多起来了,代码会显得非常丑陋。

http.get("url", param, (err, res1) => { console.log("res1: ", res1) http.get("url", res1, (err, res2) => { console.log("res2: ", res2) http.get("url", res2, (err, res3) => { console.log("res3: ", res3) ... }) }) })

从上面代码可以看出,仅仅嵌套三次已经有堆砌 山趋势了。。。
Promise ES6 引入了 js 异步编程新方案 Promise,通过 Promise 的链式调用最起码让代码看起来整齐划一了不少,一旦代码堆砌起来还是影响阅读和维护
function queryData(url, param) { return new Promise((resolve, reject) => { http.get(url, param, (err, data) => { resolve(data) }) }) } queryData("/api/user", data) .then(res1 => queryData("/api/xx", res1)) .then(res2 => queryData("/api/xx", res2)) .then(res3 => queryData("/api/xx", res3)) .then(finalRes => console.log("finalRes: ", finalRes)) .catch(error => console.log("error: ", error))

async/await 最终我们在 ES7 中迎来了青天大老爷——async\await,使用它们就可以从上到下逐行地编写我们的异步代码,进而从视觉上达到同步效果,阅读起来相当舒服,例如以上代码可以改写如下
async function request() { const res1 = await queryData("/api/user", data) const res2 = await queryData("/api/user", res1) const res3 = await queryData("/api/user", res2) const finalRes = await queryData("/api/user", res3) console.log("finalRes: ", finalRes) }

这样写瞬间就清爽了。tip: async/await 必须一起使用,否则会报错。那么它的原理是什么呢?首先我们来了解一下协程的概念
协程 协程有点像线程, 但是是一个更轻量级的存在,可以由我们自己写程序来管理。它的步骤大概如下:
  1. 协程 A 开始执行
  2. 协程 A 执行到一半,进入暂停,将执行权转移给协程 B
  3. 一段时间后协程 B 交还执行权,协程 B 进入暂停
  4. 协程 A 恢复执行
Genarator 函数 genarator 函数是 js 里协程的一种实现,它的声明方式如下:
function* gen() { yield 1 yield 2 yield 3 }

函数名前需要加一个星号, 函数体内需要有 yield 关键字。genarator 函数是一个生成器, 调用后并不执行函数体,而是返回一个 iterator 对象,通过调用 iterator 对象上的 next 方法来执行 genarator 函数,首次调用执行到 yield 关键字就暂停,再次调用执行到下一个 yield 关键字,以此类推。
这次10分钟就帮你搞定async/await原理!
文章图片

这次10分钟就帮你搞定async/await原理!
文章图片

这次10分钟就帮你搞定async/await原理!
文章图片

这次10分钟就帮你搞定async/await原理!
文章图片

调用 iterator 对象的 next 方法会返回一个对象 {value: ..., done: ...},value 为每次执行到 yield 时后面跟的值, done 是一个布尔值, false 表示函数没有执行完后面还有 yield, true 表示执行完毕,没有 yield 了。当 done 为 true 时,value 为函数的返回值。
【这次10分钟就帮你搞定async/await原理!】这次10分钟就帮你搞定async/await原理!
文章图片

这次10分钟就帮你搞定async/await原理!
文章图片

调用 iterator 的 next 方法时还可以传参,参数可以在 yield 后面的赋值语句中进行赋值。如果不进行传参则会丢失 yield 后面的值。
这次10分钟就帮你搞定async/await原理!
文章图片

这次10分钟就帮你搞定async/await原理!
文章图片

这次10分钟就帮你搞定async/await原理!
文章图片

这次10分钟就帮你搞定async/await原理!
文章图片

对比 async/await 与 genarator 函数会发现
  1. async 函数调用返回 promise,genarator 函数调用返回 iterator 对象
  2. async 函数无需通过手动调用的方式执行函数体,也就是说它自带执行器
  3. 用 async/await 代替*/yield 拥有更好的语义性
那么我们来炫一个,使用 genarator 函数和 Promise 实现我们的 async 函数。
async function someFunction(args) { //... }

等同于:
function fn(num) { return new Promise(resolve => { setTimeout(() => { resolve(num * 2) }, 1000) }) } function* gen(param) { const res1 = yield fn(param) console.log("res1-->", res1) const res2 = yield fn(res1) console.log("res2-->", res2) const res3 = yield fn(res2) console.log("res3-->", res3) return res3 }function genToAsync(gen) { return function () { const g = gen.apply(this, arguments) return new Promise((resolve, reject) => { function run(val) { let result try { result = g.next(val) } catch (err) { return reject(err) } const { value, done } = result if (done) { resolve(value) return } return Promise.resolve(value) .then(res => { run(res) }) .catch(err => { reject(err) }) } run() }) } } const asyncGen = genToAsync(gen) asyncGen(2).then(res => console.log("res-->", res))

执行结果
这次10分钟就帮你搞定async/await原理!
文章图片

由以上代码我们可以看到实现了和 async/await 一样的效果

    推荐阅读