• Generator函数


    Generator函数

    基本概念

    Generator(生成器) 函数是 ES6 提供的一种异步编程解决方案,并且Generator函数的行为与传统函数完全不同。

    定义Generator函数

    function* f() {
        
    }
    

    形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部可以使用yield关键字,定义不同的内部状态(yield在英语里的意思就是“产出”)。

    执行Generator函数

    执行 Generator 函数,函数本身不会执行,而是会返回一个遍历器对象,同时该对象也是可遍历的,因为在其原型链上也具有Symbol.iterator方法,并且改方法返回的对象就是该遍历器对象自身

    function* f() {
      console.log(1)
    }
    let a = f()
    a[Symbol.iterator]() === a // true
    

    因此,Generator函数返回的对象也可以被遍历,相当于每次调用此对象next()value来作为遍历结果

    只有执行了该遍历器对象的next()方法,Generator函数才会执行:

    function* f() {
       console.log(1)
    }
    let a = f()
    a.next() // 打印1 返回{value:undefined,done:true}
    

    yield 和 yield*

    Generator函数中可以使用yield关键字来定义函数返回的遍历器对象每次next()后的value

    function* f() {  
        yield 1
    }
    let a = f()
    a.next()  // 返回 {value: 1, done: false}
    

    并且a每次执行next(),都会在下一个yield处暂停,直到后面没有yield关键字,则执行剩余代码,并且返回done:true

    function* f() {
      console.log('step1')
      yield 1
      console.log('step2');   
      yield 2
      console.log('step3');
    }
    let a = f()
    a.next() // 打印step1  返回 {value: 1, done: false}
    a.next() // 打印step2  返回 {value: 2, done: false}
    a.next() // 打印step3  返回 {value: undefined, done: true}
    

    yield本身没有返回值,yield的返回值是下一次next()函数传入的值。

    所以next()方法的作用有两个

    1. 执行本次yield到下一个yield之间的代码
    2. 将形参的值传给本次yield的返回值

    next()yield实现了函数内外控制权的转移。

    function* f() {  
        console.log('start');  
        let result = yield 1  
        console.log('result:',result);
    }
    let a = f()
    

    yield* 等同于遍历某个对象,并且yield每个结果

    function* f() {  
        yield 'start'  
        yield* [1, 2, 3]  
        /*等同于*/  
        // for(let value of [1,2,3]){  
        //   yield value  
        // }  
        yield 'end'
    }
    let a = f()
    a.next() // 返回 {value: 'start', done: false}
    a.next() // 返回 {value: 1, done: false}
    a.next() // 返回 {value: 2, done: false}
    a.next() // 返回 {value: 3, done: false}
    a.next() // 返回 {value: 'end', done: false}
    a.next() // 返回 {value: undefined, done: true}
    

    Generator函数配合自动执行器

    直接循环存在的问题

    Generator函数是一种新的异步编程解决方案,但是每次手动调用next()很麻烦,如果我们写一个循环来执行next()呢?

    function* f() {
      yield 1
      console.log('完成1');
      yield 2
      console.log('完成2');
    }
    let it = f()
    let done = false
    while (!done){
      done = it.next().done
    }
    

    看似是没有问题,但是如果yield后面本身就是一个异步操作,就会有问题

    function* f() {
      yield readFile(file1)
      console.log('耶,完成了1');
      yield readFile(file2)
      console.log('耶,完成了2');
    }
    let it = f()
    let done = false
    while (!done){
      done = it.next().done
    }
    //耶,完成了1
    //耶,完成了2
    

    如果我们的需求是让异步操作执行完毕后再执行yield后面的代码,那么上述执行顺序就不符合需求。验证:

    function* f() {
      yield readFile(file1,function (err,data) {
        console.log('读取到数据1:' + data)
      })
      console.log('耶,完成了1');
      yield readFile(file2,function (err,data) {
        console.log('读取到数据2:' + data)
      })
      console.log('耶,完成了2');
    }
    
    let it = f()
    let done = false
    while (!done){
      done = it.next().done
    }
    //耶,完成了1
    //耶,完成了2
    //读取到数据1:111
    //读取到数据2:222
    

    Thunk函数

    在 JavaScript 语言中,Thunk 函数是指将多参数函数,将其替换成一个只接受回调函数作为参数的单参数函数。

    // Thunk版本的readFile(单参数版本)
    const {readFile} = require('fs')
    const path = require('path')
    const file1 = path.join(__dirname,'./text/1.txt')
    const file2 = path.join(__dirname,'./text/2.txt')
    let Thunk = function (fileName) {
      return function (callback) {
        return readFile(fileName, callback);
      };
    };
    
    let readFileThunk = Thunk(file1);
    readFileThunk(function(err,data){
      console.log(String(data));
    });
    

    有一个thunkify库可以方便的将api变成Thunk函数

    自动执行器

    写一个自动执行器run函数,每次将it.next()的逻辑封装到nextStep()中,并且将nextStep作为回调函数传给Thunk化后的读取文件函数。

    // Thunk版本的readFile(单参数版本)
    const {readFile} = require('fs')
    const path = require('path')
    const file1 = path.join(__dirname, './text/1.txt')
    const file2 = path.join(__dirname, './text/2.txt')
    let Thunk = function (fileName) {
      return function (callback) { //result.value
        return readFile(fileName, callback);
      };
    };
    
    function* f() {
      let data1 = yield Thunk(file1)
      console.log('耶,完成了1,数据是' + data1);
      let data2 = yield Thunk(file2)
      console.log('耶,完成了2,数据是' + data2);
    }
    
    function run(f) {
      let it = f();
    
      function nextStep(err, data) {
        var result = it.next(data);
        if (result.done) return;
        result.value(nextStep);  //执行readFile,并且把nextStep作为回调传入
      }
    
      nextStep();
    }
    
    run(f)
    

    如此一来,基于自动执行器,只要异步操作是Thunk函数或者返回Promise的情况下,写异步逻辑在形式上就如同写同步逻辑一样,非常简洁。

    co模块

    co模块是对一个封装的更好的自动执行器,它支持yield的类型,不光包含thunk函数,还有Promise对象,数组,对象,甚至Generator函数

    const { promisify } = require("util");
    const path = require('path')
    const file1 = path.join(__dirname, './text/1.txt')
    const file2 = path.join(__dirname, './text/2.txt')
    const readFileP = promisify(readFile)
    let Thunk = function (fileName) {
      return function (callback) { //result.value
        return readFile(fileName, callback);
      };
    };
    
    /*Thunk*/
    function* f() {
      let data1 = yield Thunk(file1)
      console.log('耶,完成了1,数据是' + data1);
      let data2 = yield Thunk(file2)
      console.log('耶,完成了2,数据是' + data2);
    }
    
    /*Promise*/
    function* f() {
      let data1 = yield readFileP(file1)
      console.log('耶,完成了1,数据是' + data1);
      let data2 = yield readFileP(file2)
      console.log('耶,完成了2,数据是' + data2);
    }
    /*数组(并发)*/
    function* f() {
      let data = yield [readFileP(file1),readFileP(file2)]
      console.log('耶,完成了,数据是' + data);
    }
    
    /*对象(并发)*/
    function* f() {
      let data = yield {data1:readFileP(file1),data2:readFileP(file2)}
      console.log('耶,完成了,数据是' + JSON.stringify(data));
    }
    
    /*Generator函数*/
    function* f() {
      function* f1() {
        return yield {data1:readFileP(file1),data2:readFileP(file2)}
      }
      let data = yield f1()
      console.log('耶,完成了,数据是' + JSON.stringify(data));
    }
    co(f)
    

    经过一个co模块执行后的Generator函数会返回一个Promise对象:

    co(f).then(()=>{
      console.log('co执行完毕');
    })
    
  • 相关阅读:
    Delimiter must not be alphanumeric or backslash php
    mysql replace用法
    MySQL transaction
    最大子数组问题
    LUP分解法求解线性方程组
    PHP和MySQL Web开发从新手到高手,第9天-总结
    DRBD+Heartbeat+mysql 高可用性负载均衡
    优秀博客网址
    rsync+inotify 实时同步数据
    Bugfree 搭建
  • 原文地址:https://www.cnblogs.com/xm0328/p/14127991.html
Copyright © 2020-2023  润新知