ES6-Generator与异步应用

Generator函数简介

基本概念

Generator函数是ES6提供的一种异步编程解决方案

语法上理解

  • Generator函数是一个状态机,封装了多个内部状态(通过yieldreturn来定义)。
  • Generator函数还是一个遍历器生成对象,执行以后会返回一个遍历器对象,可以遍历函数内部的状态

定义形式上理解

Generator函数是一个普通的函数,有几个特征:

  • function关键字和函数名称之间存在一个 *
  • 函数体内部使用yield语句和return语句(结束执行),定义不同的内部状态
  • 函数调用以后,并不执行,返回的也不是函数的运行结果,而是一个遍历器对象,指向函数的开始位置,需要通过迭代器对象的next函数改变内部的状态

基本示例

function* gen() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var it = gen();
console.log(it.next()) // {value: 'hello', done: false}
console.log(it.next()) // {value: 'world', done: false}
console.log(it.next()) // {value: 'ending', done: true}
console.log(it.next()) // {value: 'undefined', done: true}

// 作为对象属性定义Generator函数
var obj = {
    * gen() {
        yield 1;
    },
    foo: function* () {
        yield 2;
    }
    // 以上两种方式等效
}

Generator和iterator的关联

因为Generator函数调用后会返回一个遍历器对象,该对象存在Symbol.iterator属性,执行后的结果等于自身

function* gen() {
    yield 1;
    yield 2;
    yield 3;
}
var g = gen()
console.log(g[Symbol.iterator]() === g) // true

可以把Generator函数赋值给对象的Symbol.iterator属性,从而使对象拥有iterator接口

var obj = {
    name: 'xxx',
    age : 12
}
obj[Symbol.iterator] = function* gen() {
    yield 1;
    yield 2;
    yield 3;
}
for(let i of obj) {
    console.log(i) // 1, 2, 3
}

yield语句

yield语句是Generator函数的暂停标志,

yield语句使用的注意事项

  • 只能在Generator函数中使用,不能再普通函数中使用
function* gen() {
    yield 'hello'
}
gen() // success

function nGen() {
    yield 1 // SyntaxError: Unexpected number 
}
  • yield语句如果用在一个表达式之中,必须放在圆括号里面
function* gen() {
    console.log('Hello' + yield); // SyntaxError
    console.log('Hello' + yield 123); // SyntaxError

    console.log('Hello' + (yield)); // OK
    console.log('Hello' + (yield 123)); // OK
}
  • yield语句用作函数参数或赋值表达式的右边,可以不加括号
foo(yield 'a', yield 'b'); // success
let input = yield; // success

yield语句和return语句的比较

  • yield语句和return语句都会返回后面表达式的值
  • yield语句拥有位置记忆功能,会从上一次暂停的位置继续向后执行;但是return语句只能执行一次
  • 正常函数通过return语句只能返回一个值,但是Generator函数可以通过yield语句返回一系列的值
  • for..of循环Generator的遍历器对象时,会遍历yield语句后表达式的值,但是不会遍历return后面表达式的值

yield* 语句

Generator函数中无法嵌套调用Generator函数,需要通过yield*语句来实现此功能,yield*语句不算一个状态,不影响next函数的执行与暂停

yield* + 无return的Generator函数

如果yield*后面的Generator函数没有return语句,等同于for...of遍历

function* foo() {
    yield 'a';
    yield 'b';
}
function* gen() {
    yield 'x';
    yield * foo();
    yield 'y';
}
// 等同于
// function* gen() {
//     yield 'x';
//     for (let v of foo()) {
//         yield v;
//     }
//     yield 'y';
// }
vatr it = gen()
console.log(it.next()) // {value: 'x', done: false}
console.log(it.next()) // {value: 'a', done: false}
console.log(it.next()) // {value: 'b', done: false}
console.log(it.next()) // {value: 'y', done: true}

yield* + 有return的Generator函数

如果yield*后面的Generator函数存在return语句,需要用var value = yield* iterator的形式获取return语句的值
return语句的值可接受,也可以不接受,不是必须的

function* genFuncWithReturn() {
  yield 'a';
  yield 'b';
  return 'The result';
}

function* logReturned(genObj) {
  let result = yield* genObj;
  console.log(result);
}

[...logReturned(genFuncWithReturn())]
// The result
// ['a', 'b']

yield* + array/string/Set/Map/nodeList/arguments

任何数据结构只要有Iterator接口,就可以被yield*遍历

// yield* 数组
function* gen() {
    yield 1;
    yield* [2, 3, 4];
    yield 5
}
for(let i of gen()) {
    console.log(i) // 1, 2, 3, 4, 5
}

// yield* 字符串
function* gen() {
    yield 'Hello';
    yield* 'Today';
    yield 'world';
}
for(let i of gen()) {
    console.log(i) // 'Hello', 'T', 'o', 'd', 'a', 'y', 'world'
}

next方法

next方法的运行逻辑

  • next方法运行时,遇到yield语句,就暂停执行后面的内容,直到下一次再次调用;
  • yield后面的表达式的值,会被作为next方法返回的对象的value
  • next方法如果遇不到新的yield语句,就会一直运行到函数结束;
  • 如果存在return语句,return后面表达式的值就是返回对象的value属性的值,如果不存在,value的值就是undefined

next方法的参数

yield语句本身没有返回值(undefined),如果需要其返回某个值,可以在调用next方法的时候传入参数,此参数会作为上一个yield语句的返回值

// 实例一
function* fn() {
    for(let i = 0; true; i++) {
        let res = yield i;
        if (res) {
            i = -1
        }
    }
}
var it = fn()
it.next() // 启动遍历器对象 {value: 0, done: false}
it.next() // 第二次执行 未传参 res的值为undefined {value: 1, done: false}
it.next() // 第三次执行 res的值为true i重置为-1 {value: 0, done: false}

// 实例二
function* gen(x) {
    var y = 2 * (yield (x + 1))
    var z = yield (y / 3)
    return (x + y + z)
}
var it = gen(5)
it.next() // {value: 6, done: false}
it.next() // 没有传参 y的值为NaN {value: NaN, done: false}
it.next() // return语句 done为true 没有传参 z的值为undefined {value: NaN, done: true}

// 实例三
function* gen(x) {
    var y = 2 * (yield (x + 1))
    var z = yield (y / 3)
    return (x + y + z)
}
var it = gen(5) // x = 5
it.next() // {value: 6, done: false}
it.next(12) // y = 24 {value: 8, done: false}
it.next(13) // z = 13 {value: 42, done: true}

在第一次调用next方法的时候,传入的参数无法生效,因为第一个next方法默认用来启用遍历器对象,不需要携带参数
如果需要在第一次调用next方法时传入的参数就生效,可以对Generator函数再进行一层封装

function genWrapper(fn) {
    return function (...args) {
        let it = fn(...args)
        it.next()
        return it
    }
}
var gen = genWrapper(function* (...args) {
    console.log(args)
    var one = yield 1;
    console.log(one)
    var two = yield 2;
    console.log(two)
    var three = yield 3;
    console.log(three)
})
gen('hello world!') // 内部默认调用一次next函数 ['hello world!]
gen('hello world!').next(111) // 内部已经调用过next函数 再次调用传入的参数就是上次yield的返回值 one

throw方法

Generator函数生成的遍历器对象,都有throw方法,定义在Generator.prototype原型链式上。

throw可以在外部调用,抛出异常,Generator函数体内进行try...catch异常捕获,此过程会默认执行一次next方法。

var gen = function* gen(){
    try {
        yield console.log('a');
    } catch (e) {
        // ...
    }
    yield console.log('b');
    yield console.log('c');
}

var it = gen();
it.next() // a
it.throw() // b 默认调用一次next方法
it.next() // c

Generator函数内部的try...catch只能捕获第一次throw方法抛出的异常,剩余的throw方法抛出的异常,都会被抛出函数体外,由外部进行处理。

try {
    var g = function* () {
        try {
            yield;
        } catch (e) {
            console.log('内部捕获', e) // success 内部捕获 第一次抛出错误
        }
    };
    var i = g();
    i.next();
    i.throw('第一次抛出错误')
    i.throw('第二次抛出错误');
} catch(e) {
    console.log('外部捕获', e) // success 外部捕获 第二次抛出错误
}

如果Generator函数内部没有进行异常捕获,就会被外部的catch代码捕获,如果两者都没有,代码块报错,中断运行。

function* gen(){
    yield 123;
    throw '错误'
    yield 456;
}
var it = gen()
console.log(it.next()) // success {value: 123, done: false}
console.log(it.next()) // error Uncaught 错误
  • Generator函数内部抛出的异常没有捕获的话,默认运行结束,再次调用next方法,返回对象为{value: undefined, done: true}
function* gen(){
    yield 123;
    throw '错误'
    yield 456;
}
var it = gen()
console.log(it.next()) // success {value: 123, done: false}
try {
    console.log(it.next())
} catch(e) {
    console.log(e) // success 错误;第二次执行 throw异常 被外部catch捕获
}
console.log(it.next()) // success { value: undefined, done: false }

return方法

Generator函数生成的遍历器对象,都有return方法,定义在Generator.prototype原型链式上。

  • return函数的作用是:结束Generator函数的状态变更,即结束遍历
  • return方法可以接收一个参数,作为返回对象的value值,不传默认为undefined
  • return方法会推迟到try..finallyfinally代码块执行完以后再执行
function* gen() {
    try {
        yield 1;
        yield 2;
        yield 3;
    } catch (e) {
        console.log(e)
        yield 4;
        yield 5;
    } finally {
        yield 6;
        yield 7;
        yield 8;
    }
}

var it = gen();
console.log(it.next()) // success {value: 1, done: false}
console.log(it.throw('抛出异常')) // success {value: 4, done: false}
console.log(it.return(7)) // success {value: 6, done: false}
console.log(it.next()) // success {value: 7, done: false}
console.log(it.next()) // success {value: 8, done: false}
console.log(it.next()) // success {value: 7, done: true}

Generator函数中的this

ES6规定Generator函数执行返回的遍历器是它的实例,也继承了Generator函数原型链prototype对象上的方法

function* gen() {
    yield 1;
    yield 2;
}
gen.prototype.sayHello = function () {
    console.log('Hello')
}

var g = gen()

console.log(g instanceof gen) // true
g.sayHello() // 'Hello'

Generator函数不能当做普通构造函数使用,因为它永远返回遍历器对象,而不是this对象,也不能和new命令联用

// this 指向
function* gen() {
    this.a = 'a'
}
var it = gen()
console.log(it.a) // undefined

// new 命令
function* Foo() {
    yield this.a = 1;
    yield this.b = 2;
}
new Foo() // TypeError: F is not a constructor

获取this示例的方法

利用call改变this的指向,指向一个外部定义好的空对象,此种形式,遍历器对象和this对象不是同一个示例

var obj = {}
function* Gen() {
    this.a = 1;
    yield this.b = 2;
    yield this.c = 3;
}
var it = Gen.call(obj)
console.log(obj) // {a : 1, b : 2, c : 3}
console.log(it.next()) // Object {value: 2, done: false}
console.log(it.next()) // Object {value: 3, done: false}
console.log(it.next()) // Object {value: undefined, done: false}

利用原型链,将this指向Generator函数,所有定义的变量和方法都会挂载到原型链上,而遍历器对象,也就是Generator函数的实例对象同样可以访问原型链上的内容

function* Gen() {
    this.a = 1;
    yield this.b = 2;
    yield this.c = 3;
}
var it = Gen.call(Gen.prototype)
console.log(it.next()) // Object {value: 2, done: false}
console.log(it.next()) // Object {value: 3, done: false}
console.log(it.next()) // Object {value: undefined, done: false}

console.log(it.a) // 1
console.log(it.b) // 2
console.log(it.c) // 3

利用原型链的方式,可以对Generator函数进行二次封装,将其改造成构造函数

function* gen() {
  this.a = 1;
  yield this.b = 2;
  yield this.c = 3;
}

function F() {
  return gen.call(gen.prototype);
}

var f = new F();

console.log(f.next())  // Object {value: 2, done: false}
console.log(f.next())  // Object {value: 3, done: false}
console.log(f.next())  // Object {value: undefined, done: true}

console.log(f.a) // 1
console.log(f.b) // 2
console.log(f.c) // 3

Generator函数的应用

异步操作的同步化表达

由于next方法的参数,表达的是上一次yield语句的返回值,同时Generator函数内部状态的改变,需要通过next方法来控制,可以将异步操作同步化

// seTimeout 模拟异步操作
// 先获取用户数据 再根据用户数据获取订单数据 再根据订单数据 获取商品数据
function getUsers() {
    setTimeout(() => {
        var data = '用户数据'
        it.next(data)
    }, 1000)
}

function getOrders() {
    setTimeout(() => {
        var data = '订单数据'
        it.next(data)
    }, 1000)
}

function getGoods() {
    setTimeout(() => {
        var data = '商品数据'
        it.next(data)
    }, 1000)
}

function* gen() {
    var users =  yield getUsers()
    console.log(users)
    var orders = yield getOrders()
    console.log(orders)
    var goods = yield getGoods()
    console.log(goods)
}

var it = gen()
it.next()

控制流管理

可以利用Generator函数将嵌套操作直线化(不适用于存在异步操作的情况)

function* longRunningTask(value1) {
  try {
    var value2 = yield step1(value1); // step1中调用next,并将执行结果作为参数,
    var value3 = yield step2(value2); // step2中调用next,并将执行结果作为参数,
    var value4 = yield step3(value3); // step3中调用next,并将执行结果作为参数,
    var value5 = yield step4(value4); // step4中调用next,并将执行结果作为参数,
    // Do something with value4
  } catch (e) {
    // Handle any error from step1 through step4
  }
}

var it = longRunningTask()
it.next() 

部署iterator接口

因为Generator函数返回的是一个遍历器对象,可以进行for...of遍历,所以可以利用此特性为任意对象部署iterator接口

function* objEntries(obj) {
    let keys = Object.keys(obj)
    for(let i of keys) {
        yield [i, obj[i]]
    }
}

let myObj = { foo: 3, bar: 7 };
for (let [key, value] of objEntries(myObj)) {
    console.log(key, value);
    // foo 3
    // bar 7
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值