mytac / blogs

just my blogs,只有watch才能追踪!!start只是收藏!!:boom: 更多点击=》
https://www.jianshu.com/u/5ed1bc7b3716
78 stars 18 forks source link

es6 co的使用与实现 #6

Open mytac opened 6 years ago

mytac commented 6 years ago

虽然现在异步解决方案都用async/await了,但co是一个小巧而精致的库,我们也可以学习一下他的实现原理。

什么是co

co是一个函数库,用于generator的自动执行。

我们先定义一个generator,用来异步读下面几个文件

const gen=function *(){

    const b=yield read('b.js')
    console.log(b.length)

    const c=yield read('c.js')
    console.log(c.length)
}

其中,我们定义的read异步读取文件的函数为:

function read(file){
    fs.readFile(file,'utf8',(err,res)=>{
        g.next(res)
    })
}

const g=gen()
g.next() //将分别输出两个文件的长度

co的使用

虽然上面的代码也可以顺利异步执行generator函数,但是需要把next()写到异步函数中, 增加了耦合度,而且还要创建generator的实例,代码很繁琐。

我们使用co重构上面的代码:

首先要改造一下异步函数的结构,co规定所有的异步函数必须是偏函数,称之为thunk函数:

function read(file){
    return (fn)=>{
        fs.readFile(file,'utf8',fn)
    }
}

之后,只要把generator传到co中就大功告成了

const co=require('co')

co(gen)() //分别输出两个文件的长度

实现一个简单的co

function co(fn) {
    return function(done) {
        const ctx = this;
        const gen = fn.call(ctx);
        let it = null;
        function _next(err, res) {
            if(err) res = err;
            it = gen.next(res);
            //{value:function(){},done:false}
            if(!it.done){
                it.value(_next);
            }
        }
        _next();
    }
}

这部分的核心代码为:

function _next(err, res) {
    if(err) res = err;
    it = gen.next(res);
    //{value:function(){},done:false}
    if(!it.done){
        it.value(_next);
    }
}

_next();

他的目的是递归调用generator的next(),直到done为true时,停止递归。

在调用第一次next()的之后,it为:

it={
    value:(fn)=>{fs.readFile(file,'utf8',fn)},
    done:false
}

it.donefalse的情况下,将_next()这个函数作为实参传到(fn)=>{fs.readFile(file,'utf8',fn)}中。 这时才正式执行读取文件的操作,res为读取的文件内容,之后再次执行_next()

第二次调用_next()后,将res值塞回gen中,之后执行:

 const b=res
 console.log(b.length)

之后往后执行,遇到yield又中断了,再次读取c.js中的内容,之后又进入_next(),再把res值塞回gen中,和上次一样执行下列代码:

 const c=res
 console.log(c.length)

之后再触发_next()知道done状态为true

全部代码

这里的执行顺序比较乱,强烈建议大家去加断点多跑几遍!

function co(fn) {
    return function(done) {
        const ctx = this;
        const gen = fn.call(ctx);
        let it = null;
        function _next(err, res) {
            if(err) res = err;
            it = gen.next(res);
            //{value:function(){},done:false}
            if(!it.done){
                it.value(_next);
            }
        }
        _next();
    }
}

function read(file){
    return (fn)=>{
        fs.readFile(file,'utf8',fn)
    }
}

const gen=function *(){
    const b=yield read('error.js')
    console.log(b.length)

    const c=yield read('package.json')
    console.log(c.length)
}

co(gen)()

相关链接

  1. co 函数库的含义和用法
  2. Generator 函数的含义与用法