最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • JS-Generator+async

    正文概述 掘金(Blingzf)   2021-05-14   494

    什么是Generator

    Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
    Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
    形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

    function* helloWorldGenerator() {
      yield 'hello';
      yield 'world';
      return 'ending';
    }
    
    var hw = helloWorldGenerator();
    hw.next()
    // { value: 'hello', done: false }
    
    hw.next()
    // { value: 'world', done: false }
    
    hw.next()
    // { value: 'ending', done: true }
    
    hw.next()
    // { value: undefined, done: true }
    

    这里需要补充一下遍历器对象(Iterator Object)

    Iterator

    Iterator 的遍历过程是这样的。

    (1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。

    (2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。

    (3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。

    (4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
    next方法返回一个对象,表示当前数据成员的信息。这个对象具有valuedone两个属性,value属性返回当前位置的成员,done属性是一个布尔值,表示遍历是否结束,即是否还有必要再一次调用next方法。

    function makeIterator(array) {
      var nextIndex = 0;
      return {
        next: function() {
          return nextIndex < array.length ?
            {value: array[nextIndex++]} :
            {done: true};
        }
      };
    };
    var it = makeIterator(['a', 'b']);
    it.next() // { value: "a", done: false }
    it.next() // { value: "b", done: false }
    it.next() // { value: undefined, done: true }
    

    ES6 的有些数据结构原生具备 Iterator 接口(比如数组),即不用任何处理,就可以被for...of循环遍历。原因在于,这些数据结构原生部署了Symbol.iterator属性,另外一些数据结构没有(比如对象)。凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。
    原生具备 Iterator 接口的数据结构如下:

    • Array
    • Map
    • Set
    • String
    • TypedArray
    • 函数的 arguments 对象
    • NodeList 对象
    let arr = ['a', 'b', 'c'];
    let iter = arr[Symbol.iterator]();
    
    iter.next() // { value: 'a', done: false }
    iter.next() // { value: 'b', done: false }
    iter.next() // { value: 'c', done: false }
    iter.next() // { value: undefined, done: true }
    

    next 方法的参数

    yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
    yield表达式如果用在另一个表达式之中,必须放在圆括号里面;yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号。

    function* foo(x) {
      var y = 2 * (yield (x + 1));
      var z = yield (y / 3);
      return (x + y + z);
    }
    
    var a = foo(5);
    a.next() // Object{value:6, done:false}
    a.next() // Object{value:NaN, done:false}
    a.next() // Object{value:NaN, done:true}
    
    var b = foo(5);                                 // 带参 x=5
    b.next() // { value:6, done:false }             // yield (x + 1) x+1 = 5+1
    b.next(12) // { value:8, done:false }           // 带参 (yield (x + 1))= 12
    b.next(13) // { value:42, done:true }           // 带参 (yield (y / 3))= 13
    

    for...of 循环

    下面代码使用for...of循环,依次显示 5 个yield表达式的值。这里需要注意,一旦next方法的返回对象的done属性为true,for...of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的6,不包括在for...of循环之中。

    function* foo() {
      yield 1;
      yield 2;
      yield 3;
      yield 4;
      yield 5;
      return 6;
    }
    
    for (let v of foo()) {
      console.log(v);
    }
    // 1 2 3 4 5
    

    注意:

    next()是对应yield后面表达式的,如果在for...of后在使用已经没有对应的yield了,所以会{ value: undefined, done: true },但是先next()for...of就只会根据剩余yield做显示

    var arr = [1, [[2, 3], 4], [5, 6]];
    
    var flat = function* (a) {
      var length = a.length;
      for (var i = 0; i < length; i++) {
        var item = a[i];
        if (typeof item !== 'number') {
          yield* flat(item);
        } else {
          yield item;
        }
      }
    };
    for (var f of flat(arr)) {
      console.log(f);
    };
    // 1 2 3 4 5 6
    

    next()在for...of后

    let arrs = flat(arr);
    for (var f of arrs) {
      console.log(f);
    }; 
    console.log(arrs.next());
    // 1 2 3 4 5 6 { value: undefined, done: true }
    

    next()在for...of前

    let arrs = flat(arr);
    console.log(arrs.next());
    for (var f of arrs) {
      console.log(f);
    }; 
    // { value:1, done: false } 2 3 4 5 6 
    

    斐波那契数列

    斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……在数学上,斐波那契数列以如下被以递推的方法定义:F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)

    数组

     function list(max){
    	 let arr = [];
    	 let a = 0;
    	 let b = 1;
    	 while(arr.length<max){
    		 arr.push(a);
    		 [a,b] = [b,a+b];
    	 } 
    	 return arr;
     }
     console.log(list(5));// [0,1,1,2,3]
    

    generator函数

     function* list(max){
    	let a = 0;
    	let b = 1;
    	let n = 0;
    	while(n<max){
    	  yield a;
    	  [a,b] = [b,a+b];
    	  n++;
    	};
    	return;
      };
      var arr = list(5);
      for (let item of arr) {
      	console.log(item);
      }
    // 0 1 1 2 3
    

    思考

    异步是什么

    异步的概念就是一步一步去执行,上一个事件执行完成了,下一个事件才会执行,可以对比一下if条件,异步的判断条件就是上一个事件是否执行完成。

    异步编程的实现:回调函数

    JavaScript 语言对异步编程的实现,就是回调函数。所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。

    var fs = require('fs');
    fs.readFile('./zf.js', 'utf-8', function (err, data) {
      if (err) throw err;
      console.log('data',data);
    });
    

    上面代码中,readFile函数的第三个参数,就是回调函数,也就是任务的第二段。等到操作系统返回了./zf.js这个文件以后,回调函数才会执行。

    回调函数的强耦合问题

    var fs = require('fs');
    fs.readFile(fileA, 'utf-8', function (err, data) {
        fs.readFile(fileB, 'utf-8', function (err, data) {
            //...
        });
    });
    

    以读取多个文件为例,只要有一个操作需要修改,它的上层回调函数和下层回调函数,可能都要跟着修改。这种情况就称为"回调函数地狱"(callback hell)。

    fs-readfile-promise模块,它的作用就是返回一个 Promise 版本的readFile函数。Promise 提供then方法加载回调函数,catch方法捕捉执行过程中抛出的错误。

    var readFile = require('fs-readfile-promise');
    
    readFile(fileA)
    .then(function (data) {
      console.log(data.toString());
    })
    .then(function () {
      return readFile(fileB);
    })
    .then(function (data) {
      console.log(data.toString());
    })
    .catch(function (err) {
      console.log(err);
    });
    

    Promise的最大问题

    Promise 的最大问题是代码冗余,原来的任务被 Promise 包装了一下,不管什么操作,一眼看去都是一堆then,原来的语义变得很不清楚。

    Generator函数的出现

    协程

    传统的编程语言,早有异步编程的解决方案(其实是多任务的解决方案)。其中有一种叫做"协程"(coroutine),意思是多个线程互相协作,完成异步任务。

    协程有点像函数,又有点像线程。它的运行流程大致如下。

    • 第一步,协程A开始执行。
    • 第二步,协程A执行到一半,进入暂停,执行权转移到协程B。
    • 第三步,(一段时间后)协程B交还执行权。
    • 第四步,协程A恢复执行。

    上面流程的协程A,就是异步任务,因为它分成两段(或多段)执行。

    function* asyncJob() {
      // ...其他代码
      var f = yield readFile(fileA);
      // ...其他代码
    }
    

    上面代码的函数asyncJob是一个协程,它的奥妙就在其中的yield命令。它表示执行到此处,执行权将交给其他协程。也就是说,yield命令是异步两个阶段的分界线。

    协程遇到yield命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。它的最大优点,就是代码的写法非常像同步操作,如果去除yield命令,简直一模一样。

    Generator 函数的数据交换和错误处理

    Generator 函数可以暂停执行和恢复执行,这是它能封装异步任务的根本原因。除此之外,它还有两个特性,使它可以作为异步编程的完整解决方案:函数体内外的数据交换和错误处理机制。

    next返回值的value 属性,是 Generator 函数向外输出数据;next方法还可以接受参数,向 Generator 函数体内输入数据。

    function* gen(x){
      try {
        yield;
      } catch (e){
        console.log('内部捕获',e);
      }
    }
    
    var g = gen(1);
    
    g.next()
    try {
      g.throw('a');
      g.throw('b');
    } catch (e) {
      console.log('外部捕获', e);
    }
    // 内部捕获 a
    // 外部捕获 b
    

    上面代码,Generator 函数体外,使用指针对象的throw方法抛出的错误,可以被函数体内的try...catch代码块捕获。这意味着,出错的代码与处理错误的代码,实现了时间和空间上的分离,这对于异步编程无疑是很重要的。

    Generator的自动管理

    function run(fn) {
      var gen = fn();
      function next(data) {
    	  console.log('自动循环',data)
        var result = gen.next(data);
    	console.log(result.value)
        if (result.done) return;
        next(result.value)
      };
      next();
    };
    function* f(){
    	yield 1;
    	yield 2;
    	return 3;
    }
    console.log('同步执行开始');
    run(f);
    console.log('执行结束')
    

    async...await

    function* f(){
    	yield 1;
    	yield 2;
    	return 3;
    }
    
    async function f(){
    	await 1;
    	await 2;
            await 3;
    };
    f();
    

    async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。

    (1)内置执行器。
    Generator 函数的执行必须靠执行器(自动管理模式的封装),而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。

    (2)更好的语义。
    asyncawait,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

    (3)更广的适用性。
    async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolvedPromise 对象)。

    (4)返回值是 Promise。 async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

    进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。

    系统检验

    async function async1() {
        console.log('async1 start');
        await async2();
        console.log('async1 end');
    }
    async function async2() {
        console.log('async2');
    }
    console.log('script start');
    setTimeout(function() {
        console.log('setTimeout');
    }, 0)
    async1();
    new Promise(function(resolve) {
        console.log('promise1');
        resolve();
    }).then(function() {
        console.log('promise2');
    });
    console.log('script end');
    

    let num = 10
    async function foo() {
    	console.log('async:'+await new Promise(res => {
    		setTimeout(() => console.log('async-set'+ ++num),0)
    		num++;
    		res(num);
    		num++
    	}));
    	console.log('async2:'+num)
    }
    console.log('1',++num)
    console.log('2',num++)
    setTimeout(()=>console.log('set1'+num),0)
    console.log('c1:'+num)
    foo()
    console.log('c2:'+num)
    setTimeout(()=>console.log('set2:'+num),0)
    

    参见

    1. 阮一峰-es6入门

    起源地下载网 » JS-Generator+async

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元