• Generator库co4.6使用及源码分析


    原文链接 http://www.cnblogs.com/ytu2010dt/p/6043947.html

    co4.x已经抛弃了原来thunk转而结合promise实现。

    一:promise

    promsie是es6原声支持的把一步嵌套转换为线型调用的一种方式,angular的$q和node的q包都是基于promise A+规范封装的 。原生使用方法如下

    //首先对异步操作封装
    function readFile(path) { return new Promise(function(resolve, reject) { fs.readFile(path, 'utf8', function(err, data) { if (!err) { resolve(data); } else { reject(err); } }) }); }
    // 调用如下(没次调用完返回一个新的promise,在最后的catch里处理错误)

    readFile('01.txt')
        .then(function(data) {
            if (data) {
                console.log(data)
                return readFile('02.txt')
            }
        })
        .then(function(data) {
            if (data) {
                console.log(data)
                return readFile('03.txt')
            }
        })
        .catch(function(err) {
            console.log(err)
        })

    二:Generator

    function* fn(a){
       var x=yield a;
    //x==4 var y=yield x; var z=yield y+1; } var demo=fn(1) console.log(demo.next(2))//{ value: 1, done: false } console.log(demo.next(3))//{ value: 3, done: false } console.log(demo.next(4))//{ value: 5, done: false } console.log(demo.next(5))//{ value: undefined, done: true }

    generator 函数和普通函数不通之处在与function后面需要加一个*,直接调用时不会执行,必须调用next方法才会执行。而且函数内部可以有多个yield即可以返回多个值。有点类似打断点,调用一次next方法执行一步。

    yield默认没有返回值,可以在调用next时加入参数作为上一个yield的返回值

    上面的例子给fn函数传入1,此时不执行,

    调用第一个next方法,此时a==1,此时无返回值,value==1

    调用第二个next方法传入3,即x==3,value==3

    调用第三个next传入4 即y==4  value==4+1==5

    再次调用next方法 generator执行完毕,所以value为undefined done为true

    三:co

    co是express和koa的作者tj大神基于es6 Generator和promise实现的一个解决js异步回调嵌套地狱的库除了注视100行左右代码,可谓短小精悍。

    co允许我们像写同步代码一样鞋异步代码,并且可以用try catch捕获异常。调用co始终返回一个promise所以也可以用then catch。 使用方法如下(传入一个generator function)

    注:function* (){}()是generator

         function* (){}是generator function

    co(function*() {
            try {
                var a = readFile('01.txt');
                var b = readFile('02.txt');
                var c = readFile('03.txt');
                //yield后面可以跟对象、数组、promise、Thunks、Generators、Generator Functions
                var res = yield [a, b, c];
                return res;
            } catch (err) {
                console.error(err.message); // "boom"
            }
        })
        .then(function(data) {
            console.log(data)
        })
        .catch(function(error) {
            console.log(error)
        });
    

    四:co源码

    1.调用co始终返回一个promise,这也是co后面也可以跟then catch的原因

    2.co的核心就是通过next方法不断调用 generator的next方法,直到generator执行完毕。

    3.yield 后面个跟以下几种function, promise, generator, array, or object,然后调用toPromise方法将以上几种数据格式都转化为promise

    核心代码如下

    4.可以通过co.wrap把一个generator函数转化为promise,原理是co.wrap内部调用了co函数,因为co始终返回一个promise,所以co.wrap可以将generator函数转化为promise。

    另外如果需要调用两个除了参数不同其余都相同的co,此时可以用co.wrap()返回普通函数带入实参调用。如下

    var getFile = co.wrap(function* (file){
        var filename = yield readFile(file, 'utf-8');
        return filename;
    });
    getFile('./01.txt')
    .then(function(data){
        console.log(data)
    })
    getFile('./02.txt')
    .then(function(data){
        console.log(data)
    })
    

     5.co函数如下

    function co(gen) {
      //这里的this在node中指向global
      var ctx = this;
      var args = slice.call(arguments, 1)
      //返回一个promise对象
      return new Promise(function(resolve, reject) {
        //如果gen为一个generator function则执行函数返回一个generator对象
        //相当于执行function* (){}()
        if (typeof gen === 'function') gen = gen.apply(ctx, args);
        //如果gen为空或者不是generator function则直接把gen放在promise的resolve中作为参数
        if (!gen || typeof gen.next !== 'function') return resolve(gen);
        //默认调用一次onFulfilled
        onFulfilled();
        function onFulfilled(res) {
          console.log(res)
          var ret;
          try {
            //第一次调用next不传参数
            ret = gen.next(res);
          } catch (e) {
            return reject(e);
          }
          //递归yeild
          next(ret);
        }
        //如果yeild后面跟的不是function, promise, generator, array, or object
        //的一种,则执行这里。
        function onRejected(err) {
          var ret;
          try {
            ret = gen.throw(err);
          } catch (e) {
            return reject(e);
          }
          next(ret);
        }
        function next(ret) {
          console.log(ret)
         //如果generator function执行完毕,直接返回数据(undefined)
          if (ret.done) return resolve(ret.value);
          //generator function没有执行完毕,把所有ret.value转化为promise
          var value = toPromise.call(ctx, ret.value);
          //如果转换为promise成功则执行promise
          //yeild后面只能跟function, promise, generator, array, or object
          if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
          //转换失败则抛出错误
          return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
            + 'but the following object was passed: "' + String(ret.value) + '"'));
        }
      });
    }
    

     其余就是一系列的数据结构抓换为ptomise

    //数组转化为promise,对数组用map遍历,然后把每一项转换为promise。
    //promise的all可以传入多个promise,并返回一个新的ptomise
    //所有子promise执行完毕后,这个promise才进入resolve
    function arrayToPromise(obj) {
      return Promise.all(obj.map(toPromise, this));
    }
    
    //对象转换为promise
    function objectToPromise(obj){ //新建一个空obj var results = new obj.constructor(); //返回一个obj的键数组 var keys = Object.keys(obj); //这个空数组用于存放对象中所有值转换完的promise var promises = []; //循环便利每一项,调用toPromise for (var i = 0; i < keys.length; i++) { var key = keys[i]; var promise = toPromise.call(this, obj[key]); //如果转换成为promise则push到promises if (promise && isPromise(promise)) defer(promise, key); else results[key] = obj[key]; } //Promise.all执行所有的promise然后返回一个新的promise return Promise.all(promises).then(function () { return results; }); function defer(promise, key) { results[key] = undefined; promises.push(promise.then(function (res) { results[key] = res; })); } }

    目前理解还不是特别深刻,以后有了新的理解随时补上。 

    参考文章  (1)阮老师es6入门

                 (2)搞定 koa 之 co源码

                 (3)co-4.0新变化

  • 相关阅读:
    怎样理解 C++ 11中的move语义
    面试---计算机网络
    TCP报头中的选项字段
    协程的实现之调度器
    CPU缓存
    TLS---线程局部存储
    微信libco协程设计及实现---总结
    libco源码解析---协程运行与基本结构
    2014年第五届蓝桥杯B组(C/C++)预赛题目及个人答案(欢迎指正)
    并查集(模板&典型例题整理)
  • 原文地址:https://www.cnblogs.com/ytu2010dt/p/6043947.html
Copyright © 2020-2023  润新知