取代Promise的Generator生成器函数

接触过Ajax请求的会遇到过异步调用的问题,为了保证调用顺序的正确性,一般我们会在回调函数中调用,也有用到一些新的解决方案如Promise相关的技术。

在异步编程中,还有一种常用的解决方案,它就是Generator生成器函数。顾名思义,它是一个生成器,它也是一个状态机,内部拥有值及相关的状态,生成器返回一个迭代器Iterator对象,我们可以通过这个迭代器,手动地遍历相关的值、状态,保证正确的执行顺序。

一、简单使用

1. 声明

Generator的声明方式类似一般的函数声明,只是多了个*号,并且一般可以在函数内看到yield关键字

function* showWords() {
    yield ‘one‘;
    yield ‘two‘;
    return ‘three‘;
}

var show = showWords();

show.next() // {done: false, value: "one"}
show.next() // {done: false, value: "two"}
show.next() // {done: true, value: "three"}
show.next() // {done: true, value: undefined}

如上代码,定义了一个showWords的生成器函数,调用之后返回了一个迭代器对象(即show)

调用next方法后,函数内执行第一条yield语句,输出当前的状态done(迭代器是否遍历完成)以及相应值(一般为yield关键字后面的运算结果)

每调用一次next,则执行一次yield语句,并在该处暂停,return完成之后,就退出了生成器函数,后续如果还有yield操作就不再执行了

2. yield和yield*

有时候,我们会看到yield之后跟了一个*号,它是什么,有什么用呢?

类似于生成器前面的*号,yield后面的星号也跟生成器有关,举个大栗子:

function* showWords() {
    yield ‘one‘;
    yield showNumbers();
    return ‘three‘;
}

function* showNumbers() {
    yield 10 + 1;
    yield 12;
}

var show = showWords();
show.next() // {done: false, value: "one"}
show.next() // {done: false, value: showNumbers}
show.next() // {done: true, value: "three"}
show.next() // {done: true, value: undefined}

增添了一个生成器函数,我们想在showWords中调用一次,简单的 yield showNumbers()之后发现并没有执行函数里面的yield 10+1

因为yield只能原封不动地返回右边运算后值,但现在的showNumbers()不是一般的函数调用,返回的是迭代器对象

所以换个yield* 让它自动遍历进该对象

function* showWords() {
    yield ‘one‘;
    yield* showNumbers();
    return ‘three‘;
}

function* showNumbers() {
    yield 10 + 1;
    yield 12;
}

var show = showWords();
show.next() // {done: false, value: "one"}
show.next() // {done: false, value: 11}
show.next() // {done: false, value: 12}
show.next() // {done: true, value: "three"}

要注意的是,这yield和yield* 只能在generator函数内部使用,一般的函数内使用会报错

function showWords() {
    yield ‘one‘; // Uncaught SyntaxError: Unexpected string
}

虽然换成yield*不会直接报错,但使用的时候还是会有问题,因为’one‘字符串中没有Iterator接口,没有yield提供遍历

function showWords() {
    yield* ‘one‘;
}

var show = showWords();

show.next() // Uncaught ReferenceError: yield is not defined

在爬虫开发中,我们常常需要请求多个地址,为了保证顺序,引入Promise对象和Generator生成器函数,看这个简单的栗子:

var urls = [‘url1‘, ‘url2‘, ‘url3‘];

function* request(urls) {
    urls.forEach(function(url) {
        yield req(url);
    });

//     for (var i = 0, j = urls.length; i < j; ++i) {
//         yield req(urls[i]);
//     }
}

var r = request(urls);
r.next();

function req(url) {
    var p = new Promise(function(resolve, reject) {
        $.get(url, function(rs) {
            resolve(rs);
        });
    });

    p.then(function() {
        r.next();
    }).catch(function() {

    });
}

上述代码中forEach遍历url数组,匿名函数内部不能使用yield关键字,改换成注释中的for循环就行了

3. next()调用中的传参

参数值有注入的功能,可改变上一个yield的返回值,如

function* showNumbers() {
    var one = yield 1;
    var two = yield 2 * one;
    yield 3 * two;
}

var show = showNumbers();

show.next().value // 1
show.next().value // NaN
show.next(2).value // 6

第一次调用next之后返回值one为1,但在第二次调用next的时候one其实是undefined的,因为generator不会自动保存相应变量值,我们需要手动的指定,这时two值为NaN,在第三次调用next的时候执行到yield 3 * two,通过传参将上次yield返回值two设为2,得到结果

另一个栗子:

由于ajax请求涉及到网络,不好处理,这里用了setTimeout模拟ajax的请求返回,按顺序进行,并传递每次返回的数据

 1 var urls = [‘url1‘, ‘url2‘, ‘url3‘];
 2
 3 function* request(urls) {
 4     var data;
 5
 6     for (var i = 0, j = urls.length; i < j; ++i) {
 7         data = yield req(urls[i], data);
 8     }
 9 }
10
11 var r = request(urls);
12 r.next();
13
14 function log(url, data, cb) {
15     setTimeout(function() {
16         cb(url);
17     }, 1000);
18
19 }
20
21
22 function req(url, data) {
23     var p = new Promise(function(resolve, reject) {
24         log(url, data, function(rs) {
25             if (!rs) {
26                 reject();
27             } else {
28                 resolve(rs);
29             }
30         });
31     });
32
33     p.then(function(data) {
34         console.log(data);
35         r.next(data);
36     }).catch(function() {
37
38     });
39 }

达到了按顺序请求三个地址的效果,初始直接r.next()无参数,后续通过r.next(data)将data数据传入

注意代码的第16行,这里参数用了url变量,是为了和data数据做对比

因为初始next()没有参数,若是直接将url换成data的话,就会因为promise对象的数据判断 !rs == undefined 而reject

所以将第16行换成 cb(data || url);

通过模拟的ajax输出,可了解到next的传参值,第一次在log输出的是 url = ‘url1‘值,后续将data = ‘url1‘传入req请求,在log中输出 data = ‘url1‘值

4. for...of循环代替.next()

除了使用.next()方法遍历迭代器对象外,通过ES6提供的新循环方式for...of也可遍历,但与next不同的是,它会忽略return返回的值,如

function* showNumbers() {
    yield 1;
    yield 2;
    return 3;
}

var show = showNumbers();

for (var n of show) {
    console.log(n) // 1 2
}

此外,处理for...of循环,具有调用迭代器接口的方法方式也可遍历生成器函数,如扩展运算符...的使用

function* showNumbers() {
    yield 1;
    yield 2;
    return 3;
}

var show = showNumbers();

[...show] // [1, 2, length: 2]

5. 更多使用

更多使用可参考 MDN - Generator

时间: 2024-10-11 22:16:23

取代Promise的Generator生成器函数的相关文章

引入Promise对象和Generator生成器函数

当然,如果您只有一个 subscription 的话,就不需要执行这个命令. 下面我们使用Entity Framework Core试用Code First模式,结合领域驱动设计,快速构建领域模型,假设建模后的模型包含 “用户“”和“物品“两个实体,当然这种开发模式可以充分利用面向对象的设计理念,比如领域模型通过继承获取一些系统属性:BaseEntity 这个 ASP.NET Core 站点,既用了缓存,也有数据库访问操作,是一个典型的 Web 站点,如果它能持续稳定运行,将为我们全站迁移至 A

ES6新特性三: Generator(生成器)函数详解

本文实例讲述了ES6新特性三: Generator(生成器)函数.分享给大家供大家参考,具体如下: 1. 简介 ① 理解:可以把它理解成一个函数的内部状态的遍历器,每调用一次,函数的内部状态发生一次改变. ② 写法: function* f() {} ③ 作用:就是可以完全控制函数的内部状态的变化,依次遍历这些状态. ④ 运行过程:当调用Generator函数的时候,该函数并不执行,而是返回一个遍历器(可以理解成暂停执行).通过调用next()开始执行,遇到yield停止执行,返回一个value

Generator生成器函数执行过程的理解

一个最基本的Generator函数格式如下,函数体内部要使用yield修饰符则必须在函数名前加上*号 let y = 0; function *testYield(x){ console.log('before yield') y = yield x + 1; console.log('after yield') return y; } 结合此函数的调用代码如下,调用testYield(1) 并赋值给变量g时, 函数体内的任何语句并没有执行,只是生成了一个迭代器赋值给变量g let g = te

ES6生成器函数generator

generator是ES6新增的一个特殊函数,通过 function* 声明,函数体内通过 yield 来指明函数的暂停点,该函数返回一个迭代器,并且函数执行到 yield语句前面暂停,之后通过调用返回的迭代器next()方法来执行yield语句.如下代码演示: function* generator() { yield 1; yield 2; yield 3; } var gen = generator(); 如上代码:generator函数的调用方法与普通函数一样,也是在函数名后面加上一对圆

[ES6系列-07]Generator Function: 生成器函数

[原创]码路工人 Coder-Power 大家好,这里是码路工人有力量,我是码路工人,你们是力量. github-pages 博客园cnblogs Generator function 生成器函数是ES6中新增的语法糖,本质上讲,就是以封装成一个遍历器的形式,让编码的你获得程序的执行控制权,通俗地说就是,流程控制上,踹一脚,走一步,不要太暴力~ 0.前言 要说到生成器函数,就不得不提到javascript的异步编程方式演进史. (不能跑题不能跑题不能跑题) 1.普通的回调函数方式(callbac

Day 21 三元表达式、生成器函数、列表解析

知识点程序: 1 #! /usr/bin/env python 2 # -*- coding: utf-8 -*- 3 # __author__ = "DaChao" 4 # Date: 2017/6/16 5 6 l = [i for i in range(100) if i%2 > 0] 7 print(l) 8 9 ########################### 10 #生成器 generator!!! 模拟tail grep功能 11 ##############

ES6新特性:Javascript中Generator(生成器)

ES6的很多特性都跟Generator扯上关系,而且实际用处比较广, 包含了任何需要异步的模块, 比如ajax, filesystem, 或者数组对象遍历等都可以用到: Generator的使用: Generator函数和普通的函数区别有两个, 1:function和函数名之间有一个*号, 2:函数体内部使用了yield表达式:比如这样: function* gen() { yield "1"; yield "2" } 这个玩意儿如果运行的话,会返回一个Iterat

python迭代器和生成器(3元运算,列表生成式,生成器表达式,生成器函数)

1.1迭代器 什么是迭代器: 迭代器是一个可以记住遍历的位置对象 迭代器对象从集合的第一个元素元素开始访问,直到所有元素被访问完结束,迭代器只能往前不会后退. 迭代器有两个基本方法:iter ,next 方法 内置函数iter(),next()  本质上都是用的对象.__iter__(),__next__()的方法 内置函数 iter(iterable),表示把可迭代对象 变成迭代器(iterator) 内置函数next(iterator) ,表示查看下一次迭代的值(当然也可以用 iterato

ES6学习笔记&lt;三&gt; 生成器函数与yield

为什么要把这个内容拿出来单独做一篇学习笔记? 生成器函数比较重要,相对不是很容易理解,单独做一篇笔记详细聊一聊生成器函数. 标题为什么是生成器函数与yield? 生成器函数类似其他服务器端语音中的接口(Interface),yield则是生成器函数中表示 返回 或 继续 执行的关键字. 弄清楚这两个概念后,先看一个例子: function* fun(val) { yield 1*val; yield 2*val; yield 3*val; yield 4*val; return 5*val; }