16Generator函数的方法

简介

基本概念

Generator 是 ES6 提供的一种异步编程解决方案。可以把它理解成一个状态机,并且会返回一个遍历器对象

  1. 有以下两个特征
  • function关键字和函数名之间有一个星号*
  • 函数体内部使用yield表达式
  1. 执行大致过程
  • 生成 Iterator 之后,第一次调用next方法,直到遇见第一个yield表达式为止。此时返回一个对象,value就是yield表达式的值,同时done为false
  • 按照此规律执行,当执行到return语句是,next方法还是返回一个对象,该对象的value就是return返回的值,同时done为ture
  • 如果没有return,执行完所有的yielddone还是为false。需要再来一下next

yield 表达式

  1. generator 函数中可以不使用yield,此时变成了一个单纯的暂缓执行函数
  2. 在普通函数,匿名函数中使用yield表达式,会产生一个错误
  3. 如果yield表达式在另一个表达式中,必须放在圆括号内
// 情景3示例
function* demo() {
  console.log('Hello' + (yield 99)); // OK
  console.log('Hello' + (yield 123)); // OK
}

next 方法參數

next 方法可以带一个参数,该参数会被当作上一个yield表达式的返回值

基础案例分析

function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}
var b = foo(5);
b.next();  // {value:6,done:false}
b.next(12);  // {value:8,done:false}
// 此时 x=5,y=24,z=13
b.next(13);  // {value:42,done:true}
  1. 第一个使用next不需要传值

进阶案例分析

function* dataConsumer() {
  console.log('Started');
  console.log(`1. ${yield}`);
  console.log(`2. ${yield}`);
  return 'result';
}
let genObj = dataConsumer();
genObj.next();
genObj.next('a');
genObj.next('b');
  1. 第一次调用next,执行到第二条语句暂停。因此打印'Started'
  2. 第二次调用next,传入a,作为上次yield的返回值,打印1. a。继续执行到yield暂停
  3. 第三次调用next,传入b,作为上次yield的返回值,答应2. b。继续执行,此时{value:'result',done:true}

for...of循环

斐波那契数列

function* feibonacci() {
  let [prev,curr] = [0,1];
  for (;;) {
    [prev,curr] = [curr,prev+curr]
    yield curr;
  }
}
// num 是值的大小,不是个数
for (let num of feibonacci()) {
  if (num > 100) break; // ---> break 会触发遍历器的return方法,return 方法其实一般就是返回`return {done:true}`
  console.log(num);
}

对象扩展遍历器

在对象的[Symbol.iterator]上挂载遍历器

function* objectEntries() {
  let objKeys = Object.keys(this);
  for (let key of objKeys) {
    yield [key,this[key]]
  }
}
let jane = { first: 'Jane', last: 'Doe'};
// 只能绑定到单个的对象上
jane[Symbol.iterator] = objectEntries;
for (let [key,val] of jane) {
  console.log(key,val)
}

throw方法

遍历器对象都有一个throw方法,在外部抛出错误之后,内部能够捕获

基础知识

外部抛出错误

var g = function* () {
  try {
    yield;
  } catch (e) {
    console.log('内部捕获',e)
  }
};
var i = g();
i.next();
try {
  i.throw('a');
  i.throw('b');
} catch (e) {
  console.log('外部捕获',e);
}
// 内部捕获 a
// 外部捕获 b
  1. 遍历器i执行next方法
  2. 遍历器i在外部抛出错误,被内部的捕获。同时,throw方法传递的参数当作catch的参数
  3. 遍历器再次抛出错误,catch已经执行过了,就不执行了,因此被外部的try catch捕获
  4. 可以内部捕获外部抛出的错误,也可以外部捕获抛出的错误
  5. throw方法,也就是抛出错误的方法,默认执行一次next

内部抛出错误

function* foo() {
  var x = yield 3;
  var y = x.toUpperCase();
  yield y;
}

var it = foo();

it.next(); // { value:3, done:false }

try {
  it.next(42);
} catch (err) {
  console.log(err);
}
  1. 由于next(42)后,x为数字42没有toUpperCase方法。因此,将抛出一个错误
  2. 抛出的错误将在外部被捕获

return方法

  1. 使用return方法
  • 当有参数时,返回给定的值
  • 当没有参数,返回undefined
  1. Generator函数内有try...finally代码块,那么return方法会推迟到finally代码执行完再执行
function* gen() {
  yield 1;
  yield 2;
}
var g = gen();
g.next(); // {value: 1,done: false}
g.return('foo'); // {value: 'foo',done: true}
g.next(); // {value: undefined,done: false}
// finally
function* numbers () {
  try {
    yield 2;
  } finally {
    yield 4;
  }
  yield 6;
}
var g = numbers();
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false } --- 执行finally中语句
g.next() // { value: 7, done: true } --- 执行完finnally中语句之后,再执行return语句

next,throw,return 共同点

它们作用都是让 Generator 函数回复执行

yield*表达式

yield*表达式,代表它后面是一个遍历器对象,在没有return语句的情况下,相当于使用for of

  1. Generator函数中不能直接调用Generator函数,需要使用yield*

基本案例

function* foo() {
  yield 'a';
  yield 'b';
}
function* bar() {
  yield 'x';
  yield* foo();
  yield 'y';
}
// Generator bar 相当于
function* bar() {
  yield 'x';
  for (let v of foo()) {
    yield v;
  } 
  yield 'y';
}
// 调用 bar
for (let v of bar()) {
  console.log(v);
}
// 结果是:x,a,b,y

yield*后边值的种类

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

  1. yield* 'hello'
  2. yield* [1,2,3]

有return的Generator函数

yield* foo(),如果foo函数有return,那么该表达式是有返回值的

function *foo() {
  yield 2;
  yield 3;
  return "foo";
}

function *bar() {
  yield 1;
  var v = yield *foo();
  console.log( "v: " + v );
  yield 4;
}

var it = bar();

it.next()
// {value: 1, done: false}
it.next()
// {value: 2, done: false}
it.next()
// {value: 3, done: false}
it.next();
// 相当于没有调用 next 方法,只是将返回值返回给表达式
// "v: foo"
// {value: 4, done: false}
it.next()
// {value: undefined, done: true}

取出嵌套数组

function* tree(arr) {
    if (Array.isArray(arr)) {
        for (let i = 0;i < arr.length;i++) {
            yield* tree(arr[i]);
        }
    }else {
        yield arr;
    }
}
const arr = [1,[2,3],[4,5],6,7]
for (let i of tree(arr)) {
    console.log(i);
}
  1. 注意两个地方
  • 如果使用yield tree,也就是没有*,那么调用Generator方法是没有什么效果的
  • 使用let声明的变量进行递归没有关系

作为对象属性的Generator函数

简写成下面的形式

let obj = {
  * myGeneratorMethod() {

  }
}

Generator 函数的this

Generator的返回值

  1. Generator 函数总是返回一个遍历器,这个遍历器是 Generator 的实例
  2. 使用instanceof检测,为true
  3. Generator 原型上的方法,将被遍历器继承
function* p() {

}
p.property.say = function () {
  console.log('hello')
}
let s = p();
s instanceof p;  // true
s.say();  // 'hello'

Generator 中 this 基础知识

  1. 即使“返回值”是“Generator”函数实例,但是“Generator”中this上绑定的属性,“返回值”上并不会有
  2. 不能对 Generator 函数直接使用 new 命令

应用

异步操纵的同步化表达

// loading 应用
function* loadUI() {
  showLoadingScreen();
  yield loadUIDataAsynchronously();
  hideLoadingScreen();
}
var loader = loadUI();
loader.next();
loader.next();
  1. 第一次执行next方法时,展示加载页面,同时发送异步请求
  2. 其实第二次执行next方法,应该是异步请求完毕的时候
// Ajax 应用
function request(url){
  // 是个伪代码,其实就是一个发送 ajax 请求的方法
  makeAjaxCall(url,function (response) {
    it.next(reponse);
  })
}
function* main() {
  var result = yield request('http://some.url');
  var resp = JSON.parse(result);
  console.log(resp.value)
}
var it = main();
it.next();
  1. 第一次执行next方法,发送ajax请求
  2. 在成功响应的回调函数中,第二次执行next方法,该方法将response当作上一次的返回值

控制流管理

这里只介绍最简单的一种方法

let steps = [step1Func, step2Func, step3Func];

function *iterateSteps(steps){
  for (var i=0; i< steps.length; i++){
    var step = steps[i];
    yield step();
  }
}
// 之后使用 for of 即可
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容

  • 在此处先列下本篇文章的主要内容 简介 next方法的参数 for...of循环 Generator.prototy...
    醉生夢死阅读 1,440评论 3 8
  • 简介 基本概念 Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。本章详细介绍...
    呼呼哥阅读 1,074评论 0 4
  • 异步编程对JavaScript语言太重要。Javascript语言的执行环境是“单线程”的,如果没有异步编程,根本...
    呼呼哥阅读 7,308评论 5 22
  • 本文作者就是我,简书的microkof。如果您觉得本文对您的工作有意义,产生了不可估量的价值,那么请您不吝打赏我,...
    microkof阅读 23,728评论 16 78
  • 《爱的五种语言》第46天: 服务行动,精心时刻 平锅带儿子去理发,外加拍证件照,拍完发照片给我,儿子今天的发型好帅...
    路西法妈妈阅读 170评论 0 0