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

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

一、简单使用

1. 声明

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

  1. function* showWords() {
  2. yield 'one';
  3. yield 'two';
  4. return 'three';
  5. }
  6.  
  7. var show = showWords();
  8.  
  9. show.next() // {done: false, value: "one"}
  10. show.next() // {done: false, value: "two"}
  11. show.next() // {done: true, value: "three"}
  12. show.next() // {done: true, value: undefined}

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

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

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

2. yield和yield*

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

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

  1. function* showWords() {
  2. yield 'one';
  3. yield showNumbers();
  4. return 'three';
  5. }
  6.  
  7. function* showNumbers() {
  8. yield 10 + 1;
  9. yield 12;
  10. }
  11.  
  12. var show = showWords();
  13. show.next() // {done: false, value: "one"}
  14. show.next() // {done: false, value: showNumbers}
  15. show.next() // {done: true, value: "three"}
  16. show.next() // {done: true, value: undefined}

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

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

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

  1. function* showWords() {
  2. yield 'one';
  3. yield* showNumbers();
  4. return 'three';
  5. }
  6.  
  7. function* showNumbers() {
  8. yield 10 + 1;
  9. yield 12;
  10. }
  11.  
  12. var show = showWords();
  13. show.next() // {done: false, value: "one"}
  14. show.next() // {done: false, value: 11}
  15. show.next() // {done: false, value: 12}
  16. show.next() // {done: true, value: "three"}

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

  1. function showWords() {
  2. yield 'one'; // Uncaught SyntaxError: Unexpected string
  3. }

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

  1. function showWords() {
  2. yield* 'one';
  3. }
  4.  
  5. var show = showWords();
  6.  
  7. show.next() // Uncaught ReferenceError: yield is not defined

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

  1. var urls = ['url1', 'url2', 'url3'];
  2.  
  3. function* request(urls) {
  4. urls.forEach(function(url) {
  5. yield req(url);
  6. });
  7.  
  8. // for (var i = 0, j = urls.length; i < j; ++i) {
  9. // yield req(urls[i]);
  10. // }
  11. }
  12.  
  13. var r = request(urls);
  14. r.next();
  15.  
  16. function req(url) {
  17. var p = new Promise(function(resolve, reject) {
  18. $.get(url, function(rs) {
  19. resolve(rs);
  20. });
  21. });
  22.  
  23. p.then(function() {
  24. r.next();
  25. }).catch(function() {
  26.  
  27. });
  28. }

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

3. next()调用中的传参

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

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

  1. function* showNumbers() {
  2. yield 1;
  3. yield 2;
  4. return 3;
  5. }
  6.  
  7. var show = showNumbers();
  8.  
  9. for (var n of show) {
  10. console.log(n) // 1 2
  11. }

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

  1. function* showNumbers() {
  2. yield 1;
  3. yield 2;
  4. return 3;
  5. }
  6.  
  7. var show = showNumbers();
  8.  
  9. [...show] // [1, 2, length: 2]

5. 更多使用

更多使用可参考 MDN - Generator

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

  1. ES6笔记(5)-- Generator生成器函数

    系列文章 -- ES6笔记系列 接触过Ajax请求的会遇到过异步调用的问题,为了保证调用顺序的正确性,一般我们会在回调函数中调用,也有用到一些新的解决方案如Promise相关的技术. 在异步编程中,还 ...

  2. Generator生成器函数

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

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

    本文实例讲述了ES6新特性三: Generator(生成器)函数.分享给大家供大家参考,具体如下: 1. 简介 ① 理解:可以把它理解成一个函数的内部状态的遍历器,每调用一次,函数的内部状态发生一次改 ...

  4. Generator(生成器)函数

    一.基础知识 Generator函数是ES6出现的一种异步操作实现方案. 异步即代码分两段,但是不是连续执行,第一段执行完后,去执行其他代码,等条件允许,再执行第二段. 同步即代码连续执行. 1. G ...

  5. javascript异步编程之generator(生成器函数)与asnyc/await语法糖

    Generator 异步方案 相比于传统回调函数的方式处理异步调用,Promise最大的优势就是可以链式调用解决回调嵌套的问题.但是这样写依然会有大量的回调函数,虽然他们之间没有嵌套,但是还是没有达到 ...

  6. es6 Generator生成器函数

    生成器函数使用function*声明. 在生成器函数内部,有一种类似return的语法:关键字yield.二者的区别是,普通函数只可以return一次,而生成器函数可以yield多次(当然也可以只yi ...

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

    一个最基本的Generator函数格式如下,函数体内部要使用yield修饰符则必须在函数名前加上*号 ; function *testYield(x){ console.log('before yie ...

  8. Python进阶-V 迭代器(Iterator)、生成器(Generator)函数

    一.迭代器 1.可循环的有哪些,即可用for语句或者while语句的数据类型有哪些? 字符串(str).列表(list).元组(tuple).字典(dic).集合(set).枚举类(enumerate ...

  9. 石川es6课程---13-16、generator-认识生成器函数

    石川es6课程---13-16.generator-认识生成器函数 一.总结 一句话总结: ` generator函数,中间可以停,到哪停呢,用 yield 配合,交出执行权 ` 需要调用next() ...

随机推荐

  1. MyBatis+Spring实现基本CRUD操作

    一.MyBaits介绍   MyBatis 是一个可以自定义SQL.存储过程和高级映射的持久层框架.MyBatis 摒除了大部分的JDBC代码.手工设置参数和结果集重获.MyBatis 只使用简单的X ...

  2. 通俗解释IOC原理

    1. IoC理论的背景 我们都知道,在采用面向对象方法设计的软件系统中,它的底层实现都是由N个对象组成的,所有的对象通过彼此的合作,最终实现系统的业务逻辑. 图1:软件系统中耦合的对象 如果我们打开机 ...

  3. C++ Review

    #include "iostream" #include "iomanip" #include "cstdio" using namespa ...

  4. AQS同步组件及ReentrantLock和synchronized的区别

    AQS同步组件 CountDownLatch(只有一个线程对他进行操作): 主线程必须在启动其它线程后立即调用await()方法.这样主线程的操作就会在这个方法上阻塞,直到其它线程完成各自的任务. S ...

  5. C++中的垃圾回收和内存管理

    最开始的时候看到了许式伟的内存管理变革系列,看到性能测试结果的时候,觉得这个实现很不错,没有深入研究其实现.现在想把这个用到自己的一个项目中来,在linux下编译存在一些问题,所以打算深入研究一下. ...

  6. 【BZOJ3450】Easy [期望DP]

    Easy Time Limit: 10 Sec  Memory Limit: 128 MB[Submit][Status][Discuss] Description 某一天WJMZBMR在打osu~~ ...

  7. python module: csv

    转自:sislcb 读 syntax : reader(csvfile[, dialect='excel'][, fmtparam]) csvfile:需要是支持迭代(Iterator)的对象,并且每 ...

  8. 关于k Line Chart (k线图)

    K Line Chart python实现k线图的代码,之前找过matplotlib中文文档但是画k线图的finance方法已经弃用了.所以自己在网上搜寻一下加上改编,很好的实现出k线图, 代码如下: ...

  9. RabbitMQ消息队列(一): 简单队列

    1. 示例选用python的pika模块进行测试,需要预先安装pika模块: https://pypi.python.org/pypi/pika/0.10.0#downloads 上述地址下载源码,加 ...

  10. Codeforces 362E Petya and Pipes 费用流建图

    题意: 给一个网络中某些边增加容量,增加的总和最大为K,使得最大流最大. 费用流:在某条边增加单位流量的费用. 那么就可以2个点之间建2条边,第一条给定边(u,v,x,0)这条边费用为0 同时另一条边 ...