第三篇:yield 异步篇
发布在深入理解 Yield2014年1月20日view:4650
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

上一篇(http://www.html-js.com/article/1716)讲了yield的迭代功能,那是yield的最本质的目的。我个人觉得,其异步功能,倒是有点像副作用,不过大家好像更关注yield处理异步的功能。。。

原理

yield处理异步的原理,第一篇的最后已经讲了。这里结合代码来说明一下。下面的代码,模拟从你输出到从0到n-1,输出的两个数之间,停顿500ms:

function sleepTask(time){
    return {
        begin:function(gen){
            setTimeout(function(){
                var gNext = gen.next();//-----(1)----
                if(gNext.done) return;
                gNext.value.begin(gen);
            },time)         
        }
    }
}   

//taskManager function
function tm(genFunc,ctx){
    var gen = genFunc(ctx);
    gen.next().value.begin(ctx);
}

//-------(2)------
tm(
    function* async(n){
        for(var i =0;i<n;i++){
            console.log(i);
            yield sleepTask(500);
        }
    },
    10
);

说明一下:

  • 1.一个异步操作就是一个task,这里只有一个sleepTask,就是暂停 time 毫秒。对于每个task,这里要求必须返回一个带有 begin 方法的object。遵守这个约定,task manager就能进行管理(后面多个task的协作也需要这个约定)
  • 2.sleepTask 里的begin方法,在这里实现的很丑陋,这是因为tm函数的功能太弱。实际上,tm写好了,sleepTask完全可以写成下面这样。这里,因为简(wo)单(lan),所以先这样了。

    function sleepTask(time){
        return {
            begin:function(next){
                setTimeout(next,time);      
            }
        }
    }
    

分析一下代码执行流程:

  • 1.给tm传一个GeneratorFunction(下面简称GF)
  • 2.GF函数并不是立即执行,而是返回一个Generator Object(以下简称gen),GF实际上并没有执行的控制权。
  • 3.外面调用 gen.next(),控制权到了GF函数,执行直到yield处,“暂停”
  • 4.暂停后,控制权返回给外面,外面在合适的地方,继续执行gen.next(),控制权又到了GF函数,执行直到下一个yield处,“暂停”,在让出控制权。。。如此反复直到迭代完毕

实际上搞清楚其流程,特别是控制权的流转,基本也就明白了。

优势:

上面的例子实在说明不了yield处理异步的优势。就上面的功能,实际上使用setTimeout加最简单的闭包,很快就搞定了。

但是就算这样,注意到,tm函数和sleepTask完全可以作为库,事先提供,所以使用者其实只需要写(2)处的代码即可。(2)处的代码还是挺简单明了的。

来个复杂点的,0到n-1,当是偶数时,停顿500ms;奇数时,读文件。

首先,实现fileTask

function fileTask(){
    return{
        begin:function(gen){
            setTimeout(function(){
                var gNext = gen.next("file content");
                if(gNext.done) return;
                gNext.value.begin(gen);
            },500);
        }
    }

    //----------------真·伪代码-----------
    /*
        return {
            begin:function(gen){
                fs.read('filename',function(err,data){
                    //if(err) errHandler(err);
                    var gNext = gen.next(data);
                    if(gNext.done) return;
                    gNext.value.begin(gen);
                })
            }
        }
    */
}

因为我是在chrome下进行的调试,所以这里还是使用setTimeout,但是实现正常的readFile功能并不难。

另外,当使用gen.next(data)时,data会传入到GF中,作为yield的返回值(见下面的代码)。

有些教程中,会使用gen.send(data)。这个api已经被gen.next(data)取代,看这里

然后来实现上面的功能吧,

tm(
    function* async(n){
        for(var i =0;i<n;i++){
            if(i%2==0){
                yield sleepTask(500);   
                console.log('sleep:'+i);
            }
            else{
                var data = yield fileTask();
                console.log(data+':'+i);
            }
        }
    },
    10
);

/*output,且每个输出相隔500ms:
    sleep:0
    file
    sleep:2 
    file content:3 
    sleep:4 
    file content:5 
    sleep:6 
    file content:7 
    sleep:8 
    file content:9
*/

这段代码简单明确,你可以尝试用其他的异步控制流的库去实现看看,对比一下。

当然了,每个人喜欢的风格不一样,到底哪种更好,仁者见仁了。

下一步

这里只是讲了讲yield处理异步的原理,要想深入,去研究开源的东西吧。这里列举几个


本系列完结!!谢谢各位


附本篇代码

    function sleepTask(time){
        return {
            begin:function(gen){
                setTimeout(function(){
                    var gNext = gen.next();
                    if(gNext.done) return;
                    gNext.value.begin(gen);
                },time)         
            }
        }
    }   


    function fileTask(){
        return{
            begin:function(gen){
                setTimeout(function(){
                    //console.log('readFile:'+i);
                    var gNext = gen.next("file content");
                    if(gNext.done) return;
                    gNext.value.begin(gen);
                },500);
            }
        }

        /*return{
            return {
                begin:function(gen){
                    fs.read('filename',function(err,data){
                        if(err) ....
                        var gNext = gen.next(data);
                        if(gNext.done) return;
                        gNext.value.begin(gen);
                    })
                }
            }
        }*/
    }


    //taskManager function
    function tm(genFunc,ctx){
        var gen = genFunc(ctx);
        gen.next().value.begin(gen);
    }

    tm(function* async(n){
        for(var i =0;i<n;i++){
            if(i%2==0){
                yield sleepTask(500);   
                console.log('sleep:'+i);
            }
            else{
                var data = yield fileTask();
                console.log(data+':'+i);
            }
        }
    },10);
评论
发表评论
2年前
赞了此文章!
2年前

原理一节第一个代码块还有一处笔误:

//taskManager function
function tm(genFunc,ctx){
    var gen = genFunc(ctx);
    gen.next().value.begin(ctx);
}

->

//taskManager function
function tm(genFunc,ctx){
    var gen = genFunc(ctx);
    gen.next().value.begin(gen);
}
4年前

@–_—– 谢谢指正。。如果能有所帮助,万分荣幸

4年前

原理一节的第一个代码块中taskManager function笔误

//taskManager function
function tm(genFunc,param){
    var gen = genFunc(ctx);
    gen.next().value.begin(param);
}

—>

//taskManager function
function tm(genFunc,ctx){
    var gen = genFunc(ctx);
    gen.next().value.begin(gen);
}

感谢分享:)

PUBLISHED IN
深入理解 Yield

从原理上,讲讲Yield

我的收藏