欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JS 基础之异步(五):Generator

程序员文章站 2024-02-21 09:58:28
...

接着上一部分继续了解JS 基础之异步(四):Generator(生成器、迭代器源码实现)

Generator函数

function *会定义一个生成器函数,并返回一个Generator(生成器)对象,其内部可以通过 yield 暂停代码,通过调用 next 恢复执行。

调用一个生成器对象并不会马上执行里面的代码语句,而是返回一个这个生成器的迭代器(iterator)对象。当这个迭代器对象的next()方法被调用时,其内部的语句就会被执行到yield的位置,yield后紧跟需要返回的值(可以是函数或表达式)。如果遇到的yieldyield*的话,则表示将执行权交给另一个生成器函数(当前生成器暂停执行)。

注意

  • generator function内部才能使用yield/yield*命令,而generator function内部调用的或声明的其他普通函数是不能调用yield/yield*命令的。
  • 箭头函数不能使用yield,即箭头函数不能用做generator function(但可以用做async function)
// 声明式
function* generator() {}

// 表达式
let generator = function* (){}

// 作为对象属性
let obj = {
    generator: function* (){}
}

// 箭头函数不能用做generator function,报错
let obj = {
    generator: *() => {}
}

// 箭头函数可以用做 async 函数
let obj = {
    generator: async () => {}
}

在生成器中return

遍历返回对象的done值为true时迭代即结束,不对该value处理。

function* createIterator() {
    yield 'a'
    return 1
    yield 'b'
}
let iterator = createIterator()
iterator.next()		// {value: "a", done: false}
iterator.next()		// {value: 1, done: true}
iterator.next()		// {value: undefined, done: true}

所以对这个迭代器遍历,不会对值1处理。当在生成器函数中显式 return时,会导致生成器立即变为完成状态,即调用 next() 方法返回的对象的 donetrue。如果 return后面跟了一个值,那么这个值会作为当前调用 next() 方法返回的 value 值,若 return后面没有跟任何值,则返回 undefined,相当于默认 return undefined

注意:生成器函数不能当构造函数使用

function* f() {}
var obj = new f; // throws "TypeError: f is not a constructor"

Generator对象

Generator对象(生成器对象)既是迭代器,又是可迭代对象。

function* generator() {
    yield 'a'
    yield 'b'
    yield 'c'
}

var gen = generator()

// 满足迭代器协议,是迭代器
gen.next()	// {value: "a", done: false}
gen.next()	// {value: "b", done: false}
gen.next()	// {value: "c", done: false}
gen.next()	// {value: undefined, done: true}

// [Symbol.iterator]是一个无参函数,该函数执行后返回生成器对象本身(是迭代器),所以是可迭代对象
gen[Symbol.iterator]() === gen	// true

// 可以被迭代
var gen1 = generator()
[...gen1]	// ["a", "b", "c"]

yield与next

先看一个例子

function* generator() {
    var val = yield 1
    return val + 1
}

var gen = generator()
var gen1 = gen.next
console.log('1: ', gen1.value)
gen1 = gen.next
console.log('2: ', gen1.value)
gen1 = gen.next
console.log('3: ', gen1.value)
// output: 1 NaN undefined

为什么会这样喃?这是因为调用 next()方法时,如果传入了参数,那么这个参数会作为上一条执行的 yield 语句的返回值。如果没有,则上一条执行的 yield 语句的返回值默认为 NaN,因为在处理的时候,我们没有给 next 函数传值,导致 yield 语句返回值为 NaN,则 val 为NaN,NaN + 1 当然也是 NaN了。

yield*

yield * 也称为 yield 委托。作用是将执行权交给另一个生成器或可迭代对象。(当前生成器暂停执行)。yield* 表达式迭代操作数,并产生它返回的每个值。

下面看一个例子:

function* generator(index) {
    yield index + 1
    yield index + 2
    yield index + 3
}

function* mainGenerator(index) {
    yield index
    yield* generator(index)
    yield index + 100
}

var gen = mainGenerator(1)

console.log(gen.next().value)	// 1
console.log(gen.next().value)	// 2
console.log(gen.next().value)	// 3
console.log(gen.next().value)	// 4
console.log(gen.next().value)	// 101

注意:yield* 是一个表达式,不是语句,所以它会有自己的值,即为当前迭代器关闭时返回的值(即donetrue时)。

function* gnerator1() {
  yield* [1, 2, 3];
  return "gnerator1";
}

var result;
function* gnerator2() {
  result = yield* gnerator1();
}

var iterator = gnerator2();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }, 
                              // 此时 g4() 返回了 { value: "foo", done: true }

console.log(result);          // "foo"

Generator 实现及源码解读

Generatror 函数构建版本一

// cb 也就是编译过的 test 函数
function generator(cb) {
    return (function() {
        var object = {
            next: 0,
            stop: function() {}
        }
        
        return {
            next: function() {
                var ret = cb(object)
                if (ret === undefined) return {value: undefined, done: true}
                return {
                    value: ret, 
                    done: false
                }
            }
        }
    })()
}

// 如果你使用babel编译后,可以发现 test 函数变成了这样
function test() {
    var a
    return generator(function(_context) {
        while (1) {
            switch ((_context.prev = _context.next)) {
            	// 可以发现通过 yield 将代码分割成几块
                // 每次执行 next 函数就执行一块代码
                // 并且表明下次需要执行那块代码
                case 0:
                    a = 1 + 2
                    _context.next = 4
                    return 2
                case 4:
                    _context.next = 6
                    return 3
                // 执行完毕
                case 6:
                case "end":
                    return _context.stop()
            }
        }
    })
}