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()传入的值不会被使用,因为这一次调用是为了开始执行生成器函数

  • 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()呢?

unction* 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函数

// 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执行完毕');
})
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值