zoukankan      html  css  js  c++  java
  • ES6 generators in depth 一(译)

    今天在学习redux-saga时,外部链接推荐了这篇文章ES6 generators in depth,所以翻译的同时也可以加深一下对Generator的理解。

    这里对原文一些只能在高版本现代浏览器使用的API进行了替换。

    1. 概述

    Generator有两个很重要的应用:

    • 实现迭代器
    • 在异步调用后阻塞

    1.1 借助generator实现迭代器

    下面的函数将返回一个可以遍历一个对象属性的迭代器,每个属性对应一个[key, value]对:

    function* objectEntries(obj) {
        let propKeys = Object.keys(obj)
        for(let i = 0, len = propKeys.length; i < len ; i++) {
            yield [propKeys[i], obj[propKeys[i]]]
        }
    }
    
    let jane = {first: 'Jane', last: 'Doe'};
    const objectIter = objectEntries(jane)
    let item = objectIter.next()
    while(!item.done) {
        const temp = item.value
        console.log(`key: ${temp[0]}, value: ${temp[1]}`)
        item = objectIter.next()
    }
    

    结果为:

    1.2 在异步调用后阻塞

    下面代码异步的获取两个JSON文件,后面代码会被阻塞,等两个请求都返回时再继续执行。

    const axios = require('axios')
    
    function* request() {
        try {
            let promise = yield Promise.all([
                axios.get('https://zan.wilddogio.com/antd/select.json')
                .then((response) => response.data), 
                axios.get('https://zan.wilddogio.com/antd/select.json')
                .then((response) => response.data)
            ])
            promise.then(result => {
                console.log(`first: ${result[0][0].label}`)
                console.log(`second: ${result[1][0].value}`)
            })
            
    
        } catch(e) {
            console.log('Failure to read: ' + e)
        }
    }
    
    const gen = request()
    const item = gen.next()
    gen.next(item.value)
    

    结果如下:

    2. 什么是Generator

    Generator是一个可以暂停和恢复的函数,这里举一些场景:

    第一个例子假设有这样一个generator函数:

    function* genFunc() {
        console.log('First')
        yield console.log('pause')
        console.log('Second')
    }
    

    genFunc有以下两点区别于一般函数:

    • function后跟着一个*
    • 可以通过yield暂停

    调用genFunc不会执行它,它会返回一个称之为generator对象,它可以用来控制genFunc执行:
    const g = genFunc()
    genFunc()会挂起在执行体开始处,调用g.next()继续执行genFunc,
    g.next()
    结果如下:

    可以看到在打印出pause后就停止了。
    继续调用g.next()
    g.next()
    结果Second被打印出来。

    2.1 创建generator的方法

    1. 通过generator函数声明
    function* genFunc() {}
    
    1. 通过generator函数表达式声明
    const genFunc = function* (){}
    
    1. 将generator函数定义在对象字面量中
    const obj = {
        * genFunc() {}
    }
    

    4 作为一个成员函数(类函数)定义在class中

    class MyClass{
        * genFunc() {}
    }
    

    2.2 generator所扮演的角色

    generator可以扮演以下角色:

    1. 迭代器(data producers):每一个yield可以通过next()返回一个value,这意味generator可以从循环或递归中有序的获取一个序列。由于generator对象实现了Iterable接口,这些序列可以被支持ES6 iterables的构造函数所处理(翻译可能不准确需要修改原文为these sequences can be processed by any ECMAScript 6 construct that supports iterables.),比如你可以直接使用for-of和展开操作符...来直接处理这个序列。

    2. Observer(观察者 - 数据消费者):yield也可以获取next()传进来的参数,这意味着generator又变成了一个数据消费者。

    3. 协程(Coroutines - 数据生产者与消费者):(Given that generators are pausable and can be both data producers and data consumers, not much work is needed to turn them into coroutines (cooperatively multitasked tasks).

    下面会详细介绍这些角色到底是什么。

    3 Generator迭代器(数据生产者)

    在解释之前,generator对象既可以生产数据也可以消费数据,这节就阐述如何生产数据,generator实现了IterableIterator接口,再次说明了generator函数即是可迭代的,也是迭代器。

    interface Iterable {
        [Symbol.iterator]() : Iterator;
    }
    
    iterface Iterator {
        next(value? : any) : IteratorResult;
    }
    
    iterface IteratorResult {
        value : any;
        done : boolean
    }
    

    3.1 应用generator迭代器的方法

    假设我们有这个一个generator函数

    function* genFunc() {
        yield 'a';
        yield 'b';
    }
    

    以下三种是非常重要的用法:
    第一个for-of

    for (let x of genFunc()) {
        console.log(x)
    }
    // Output:
    // a
    // b
    

    第二个, 展开(...)操作符:

    let arr = [...genFunc()]; // ['a', 'b']
    

    第三个解构

    > let [x, y] = genFunc();
    > x
    'a'
    > y
    'b'
    

    3.2 来自generator的返回值

    这块比较容易理解直接跳过了^ ^

    3.3 应用generator迭代器的例子

    这里我们来完成一个类似for-of功能函数forOf

    function forOf(obj) {
        const objSymbol = Object.getOwnPropertySymbols(obj)[0]
        const o = obj[objSymbol]
        let index = 0;
        const keys = Object.keys(o)
    
        return {
            [objSymbol]() {
                return this
            },
            next() {
                if(index < keys.length) {
                    let key = keys[index]
                    index++
                    return {value: o[key], done: false}
                } else {
                    return {
                        value: undefined,
                        done: true
                    }
                }
            }
        }
    }
    
    const obj = {
        [Symbol('generator')]: {
            name: 'gen',
            age: 18
        }
    }
    
    const g = forOf(obj)
    let iter = g.next()
    while(!iter.done) {
        console.log(iter.value)
        iter = g.next()
    }
    

    结果如下:

    3.4 通过yield*递归输出

    yield*操作符可以在一个generator函数中调用另一个generator函数,我们先来一个简单的例子来说明如果产生数据,后面我们再解释如何加入输入。

    我们提取一个公共遍历打印函数

    exports.travel = function (genFunc) {
        let idx = 0
        const g = genFunc()
        let iter = g.next()
        while(!iter.done) {
            console.log(`${idx}: ${iter.value}`)
            idx++
            iter = g.next()
        }
    }
    
    const {travel} = require('./utils')
    
    function* foo() {
        yield 'a'
        yield 'b'
    }
    
    function* bar() {
        yield 'x'
        foo()
        yield 'y'
    }
    
    travel(bar)
    

    结果如下:

    可以看到foo()并没有任何输出,这时候需要yield*出场了。

    function* bar() {
        yield 'x'
        yield* foo()
        yield 'y'
    }
    

    结果如下:

    基本上,yield*就是这样工作的

    function* bar() {
        yield 'x'
        for(let value of foo()) {
            yield value
        }
        yield 'y'
    }
    

    当然yield不仅仅可以作用在generator函数上,任意可遍历的集合都是可以的:
    数组

    function* bla() {
        yield 'sequence'
        yield* ['of', 'yield']
        yield 'values'
    }
    
    travel(bla)
    

    结果如下:

    对象

    const obj = {
        * [Symbol.iterator]() {
            const keys = Object.keys(this)
            for(let i = 0, len = keys.length; i < len; i++) {
                yield this[keys[i]]
            }
        }
    }
    
    obj.name = 'gen'
    obj.age = 18
    
    
    for(let a of obj) {
        console.log(a)
    }
    
  • 相关阅读:
    cmd的操作命令导出导入.dmp文件
    转:String数组初始化
    Oracle计算时间差
    WEB-INF目录与META-INF目录的作用
    【神乎其神】这些EXCEL技巧,太神奇了,赶紧收藏!
    报错: The type ByteInputStream is not accessible due to restriction on required library
    ModelAndView对象作用
    shiro使用
    包装类型的比较,如:Integer,Long,Double
    转一个distinct用法,很有帮助
  • 原文地址:https://www.cnblogs.com/shineyao/p/7636390.html
Copyright © 2011-2022 走看看