Generator,yield的理解|8月更文挑战

678 阅读4分钟

这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战

前言

Promise作为异步编程的解决方案之一,实际还是以回调函数作为基础,并没有从语法结构来改变异步写法。Generator函数作为异步编程进一步优化解决方案,Generator函数可以在执行时暂停,后面又能从暂停处继续执行。通常在异步操作时交出函数执行权,完成后在同位置处恢复执行。所以Generator语法可以以同步的语法形式处理异步任务。

迭代器

在了解生成器函数前,有必要先认识下迭代器。在经典的for循环中,需要设置跟踪多个索引变量,代码较容易出错,为了消除这种复杂性,ES6推出的迭代器一定程度上消除了这种复杂性,迭代器是一种特殊对象,具有专门为迭代流程设计的 next() 方法。每次调用 next() 都会返回一个包含 value 和 done 属性的对象。除此之外,迭代器提供一致的符合迭代器协议接口,可以统一可迭代对象遍历方式。例如 for...of 语句可以来迭代包含迭代器的可迭代对象(如 Array、Map、Set等)。

生成器

generator是一种返回迭代器的函数,generator跟函数很像,通过function*来表示,另外generator函数中还需要包含关键字 yield。generator和函数不同的是 ,除了return语句,还可以用yield返回多次。调用生成器函数并不会立即执行生成器内部的代码,而是返回这个生成器的迭代器对象。 因为generator可以在执行过程中多次返回,所以它看上去就像一个可以记住执行状态的函数.

function* hello() {
   var a = 'b'
   yield 'a';
   return a;
}

var gen = hello();
console.log(gen);
// => hello {[[GeneratorStatus]]: "suspended", [[GeneratorReceiver]]: undefined}
console.log(gen.next())
// => {value: "a", done: false}
console.log(gen.next())
// => {value: "b", done: true}

上面代码通过调用hello(),产生了一个生成器,内部代码没有执行。调用next方法执行到yield后暂停,内部环境被保存,next执行返回一个对象,valueyield的执行结果,done表示迭代器是否完成。当迭代器完成后,done为true,value为return的值,继续执行nextvalue将为undefined

yield 关键字

yield 关键字可以用来暂停和恢复一个生成器函数。yield 后面的表达式的值返回给生成器的调用者,可以认为 yield 是基于生成器版本的 return 关键字。yield 关键字后面可以跟 任何值 或 表达式。 一旦遇到 yield 表达式,生成器的代码将被暂停运行,直到生成器的 next() 方法被调用。每次调用生成器的next()方法时,生成器都会在 yield 之后紧接着的语句继续执行。直到遇到下一个 yield 或 生成器内部抛出异常或到达生成器函数结尾或到达return语句停止。 注意,yield 关键字只可在生成器内部使用,在其他地方使用会导致语法错误。即使在生成器内部函数中使用也是如此。

next 方法

Generator.prototype.next() 返回一个包含属性 done 和 value 的对象,也可以接受一个参数用以向生成器传值。返回值对象包含的 done 和 value 含义与迭代器章节一致,没有可过多说道的。值得关注的是,next()方法可以接受一个参数,这个参数会替代生成器内部上条 yield 语句的返回值。如果不传 yield 语句返回值则为 undefined。有个特例,首次调用 next() 方法时无论传入什么参数都会被丢弃。因为传给 next() 方法的参数会替代上一次 yield 的返回值,而在第一次调用 next()方法前不会执行任何yield语句,所以首次调用时传参是无意义的。事实上能给迭代器内部传值的能力是很重要的。比如在异步流程中,生成器函数执行到yield关键字处挂起,异步操作完成后须传递当前异步值供迭代器后续流程使用。

gen = gen(); // 转换为 generator 对象
var g = gen.next(); // 第一步启动
var firstStep = g.value; // 取得第一个 yield 后面的表达式,即 1 + 2 为 3 
g = gen.next(firstStep); // 从 next 函数传入第一步的值,则 ret 值为 3
var secondStep = g.value; // 取得第二步 yield 的值,即 4
g = gen.next(secondStep); // 从 next 函数传入第二步的值, 即 num 为 4
// 由于下面没有了 yield 语句,所以直接执行完函数,ret 为 { value: 7, done: false }
g = gen.next(g.value);
console.log(g.done); // true

总结

Generator函数作为异步编程进一步优化解决方案,可以把异步的逻辑以同步的方式书写出来,更容易理解。