fangchaolong
文章57
标签24
分类10
async和await的实现原理

async和await的实现原理

async和await的实现原理: Async/Await 是一个自执行的 Generator 函数。利用 Generator 函数的特性把异步的
代码写成“同步”的形式

var fetch = require("node-fetch");
function *gen() { // 这里的 * 可以看成 async
var url = "https://api.github.com/users/github";
var result = yield fetch(url); // 这里的 yield 可以看成 await
console.log(result.bio);}
var g = gen();var result = g.next();result.value.then(data =>
data.json()).then(data 

Generator函数

传统的编程语言,早有异步编程的解决方案(其实是多任务的解决方案)。其中有一种叫做“协程”(coroutine),意思是多个线程互相协作,完成异步任务。协程有点像函数,又有点像线程,它的运行流程大致如下

  1. 第一步,协程 A 开始执行;
  2. 第二步,协程 A 执行到一半,进入暂停,执行权转移到协程 B;
  3. 第三步,(一段时间后)协程 B 交还执行权;
  4. 第四步,协程 A 恢复执行;

上面流程的协程 A,就是异步任务,因为它分成两段(或多段)执行。
举例来说,读取文件的协程写法如下:

function* asyncJob() {
// ...
var f = yield readFile(fileA);
// ...}

上面代码的函数 asyncJob 是一个协程,它的奥妙就在其中的 yield 命令。它表示执行到此处,执行权将交给其他协程。
也就是说,yield 命令是异步两个阶段的分界线。
协程遇到 yield 命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。

Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即 暂停执行)。

function* gen(x) {
var y = yield x + 2;
return y;}
var g = gen(1);g.next() // { value: 3, done: false }g.next(2) // { value: 2,done: false }

next 是返回值的 value 属性,是 Generator 函数向外输出数据;next 方法还可以接受参数,向 Generator 函数体内输入数据。

1、什么是Generator?

是ES6提供的一种异步编程解决方案,语法不同于普通函数;简单的把Generator 理解为一个状态机,封装了多个内部状态。执行Generator 函数会返回一个迭代器对象,可以通过调用迭代器next依次遍历Generator函数内部的每一个状态。

2、Generator的特征?

  1. function关键字与函数名之间有个星号;
  2. 函数内部使用yield表达式
  3. 调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象(Iterator Object)
function* gen() { 
  yield 1
  yield 2
  return 3
  yield 4
}

let g = gen(); 
console.log(g.next())   // {value: 1, done: false}
console.log(g.next())   // {value: 2, done: false}
console.log(g.next())   // {value: 3, done: true}
console.log(g.next())   // {value: undefined, done: true}

每次调用 next() 方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个 yield 表达式(或 return 语句)为止。换言之,Generator 函数是分段执行的,yield 表达式是暂停执行的标记,而 next() 方法可以恢复执行。

调用 Generator 函数,返回一个遍历器对象 (Iterator),代表 Generator 函数的内部指针。每次调用遍历器对象的 next() 方法,就会返回一个有着 value 和 done 两个属性的对象。value 属性表示当前的内部状态的值,是 yield 表达式后面那个表达式的值;done 属性是一个布尔值,表示是否遍历结束。

Generator 函数的暂停执行的效果,意味着可以把异步操作写在 yield 语句里面,等到调用 next 方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在 yield 语句下面,反正要等到调用 next() 方法时再执行。所以,Generator 函数的一个重要实际意义就是用来处理异步操作,改写回调函数。

注意:如果 return 语句后面还有 yield 表达式,那么后面的 yield 完全不生效

function* gen(x) {
  var y = 2 * (yield (x + 1))
  var z = yield (y / 3)
  return (x + y + z)
}

var a = gen(5);
a.next()    // {value: 6, done: false}
a.next()    // {value: NaN, done: false}
a.next()    // {value: NaN, done: true}

var b = gen(5);
b.next()     // {value: 6, done: false}
b.next(12)  // {value: 8, done: false}
b.next(13)  // {value: 42, done: true}

第一次调用 a 的 next() 方法时,返回 x+1 的值6。第二次运行a的 next() 方法的时候不带参数,导致 y 的值等于2 * undefined(即NaN),除以 3 以后还是NaN,因此返回对象的 value 属性也等于 NaN。第三次运行a的 next() 方法的时候不带参数,所以 z 等于undefined,返回对象的 value 属性等于5 + NaN + undefined,即NaN。

第一次调用b的 next() 方法时,返回 x+1 的值6;第二次调用 next() 方法,将上一次 yield 表达式的值设为12,因此y等于24,返回y / 3的值8;第三次调用 next() 方法,将上一次 yield 表达式的值设为13,因此 z 等于13,这时 x 等于5,y 等于24,所以 return 语句的值等于42。

注意:

  1. 在第一次使用next方法时,传递参数是无效的
  2. V8 引擎直接忽略第一次使用 next() 方法时的参数,只有从第二次使用next方法开始,参数才是有效的。
  3. 从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。

无以生计,卖文苟延

微信
支付宝