• javascript 操作流——回调的回调


    操作流是应对一个函数的执行依赖于多个异步操作的结果而产生的。这其实是事件派发的一种。用IE only的写法如下:

                document.attachEvent("onclick",function(){
                    alert("fire click");
                });
                var e = document.createEventObject();
                document.fireEvent("onclick",e);
    

    用jQuery的写法如下:

    $(document).click(function(){
      alert("fire click");
    }).trigger("click")
    

    jQuery还能对应自定义事件,不过所有库的自定义事件实现也别无二致。但这种事件派发只能针对当前指定的某个事件,有时我想,这些自定义事件有没有必要存在。

    比如一个游戏,要通过QTE来打BOSS,如S+D+F,用jQuery实现如下:

                 var keys = 0, keystateTime = 0;
                function checkInterval(){
                    if(!keystateTime){
                        keystateTime = new Date;
                    }else{
                        var now = new  Date - keystateTime;
                        if(now > 500){
                            keystateTime = keys = 0;
                        }
                    }
                }
                $(document).bind("keypress",function(e){
                    if(e.which === 115 || e.which === 100 || e.which === 102){//SDF
                        keys += e.which;
                        checkInterval();//热键的每个操作之间间隔应该很小
                        dom.fire("qte")
                    }
                });
      
                $(document).bind("qte",function(e){
                    if(keys === 115+100+102 && keystateTime){
                       console.log("开始攻击")
                    }
                });
    

    我们不能说qte事件依赖于keypress事件,准确来说,是attack这步操作依赖于三次满足条件的按下操作。keypress绑定只是更前期的准备而已。真正相关联的是在回调与回调之间。qte的回调函数是上面那三个回调的回调。如果把事件函数的回调函数当成一次操作。那么它们四个就是一个操作流。hotKey在这过程中保是一个标识,用于移除与派发的标识。

    好了,下面正式进入主题。

    使用已有资源,对已有事、物进行加工改造,使之产生目标结果的过程叫操作。

    操作分为两大类:同步操作与异步操作。同步操作,如setStyle,getAttribute, el.innerHTML= "XXX"就是同步的。异步操作见各种事件,setTimeout回调。

    同步操作是即时的,阻塞的,必然会发生的。异步操作是延迟的,非阻塞的,不可预期的。

    事件是指经过分类,拥有相同特性的导步操作的统称。这个类别名,就是它们的事件名,click, keypress, resize, scroll....

    已存的事件系统都没有针对这种复杂的回调情况设置对应的API,不过也不奇怪,许多javascript框架都是由其他语言出身的高手的写的,他们的思维是过程式或OO式,而不是函数式。异步编程在其他语言也没有像JS那么突出,像浏览器需要处理资源的加载,因此自顶向下的同步编程风格必然被回调风格所代替。如果一个回调依赖于另一个回调的结果,那么我们就陷入回调套嵌的泥沼了。

    因此要解决这问题,必须要有一个机制,能实现多路监听收集过程中的每个回调的结果触发最终回调。这个过程是不是与模块加载系统非常像。模块加载的过程,通过require方法请求多个依赖模块,将每个请求回来的模块的结果装配到框架中,待到所有依赖都存在于框加中时就执行最终回调。

    下面是我实现操作流的一些API介绍

    • 通过var flow = dom.flow(names,fn,reload)工厂方法生成一个操作流,有没有参数无所谓,反正也是调用原型上的bind方法。
    • flow.bind(names,fn,reload),实现多路监听,names为一个用逗号或空格隔开的字符串,每个单词为要监听的操作, fn为最后的回调。reload为布尔,为false时,比如最后回调依赖于其他四种回调,一旦这四个回调都成功执行后,就执行最后回调,以后此四种回调每次执行,都会立即执行最后回调;若为true,则要重新再执行这四次回调才又执行最终回调。不写默认为false。
    • flow.fire(name,ret),用于触发最后的回调,name为names中的某一单词,ret可选,它将成为最终回调的参数之一。
    • flow.unbind(names,fn),移除监听,fn可选,不存在则移除names对应的所有回调。

    好了,我们再回顾上面的QTE实现,其实现也不严谨,因为玩家可能一下子ASDFG都按了。另外,我们还要保证按键顺序,第一次必须是S,第二次是D,第三次是F。因此这种情况,使用操作流实现最合适了。上面游戏的QTE实现用mass Framework实现如下:

                 dom.require("ready,node,event,flow",function(){
                    var keystateTime = 0, i = 0;
                    function checkInterval(){
                        if(!keystateTime){
                            keystateTime = new Date;
                        }else{
                            var now = new  Date - keystateTime;
                            if(now > 500){
                                keystateTime =  0;
                            }
                        }
                    }
                    var flow = dom.flow("0_115,1_100,2_102", function(){
                        if( keystateTime){
                            i = -1;
                            dom.log("开始攻击")
                        }
                    },true);
                    dom(document).bind("keypress",function(e){
                        checkInterval();
                        dom.log(i+"_"+e.which)
                        flow.fire(i+"_"+e.which);
                        i++
                        if(i == 3 || e.which === 13  ){//按enter键重试
                            i = 0;
                        }
                    });
                });
    

    操作流有许多好处,我能说的大概被另一个类似实现EventProxy的作者说了,什么避免回调套嵌,将串行等待变成并行等待,一处合并,多处触发……EventProxy与我的操作流解决相同的问题,不同的是实现手段,我的操作流只是我的模块加载系统的简化版,使用依赖列表对应的对象的state的值来判定是否执行最后的回调,而EventProxy则由times决定是否执行最后的回调。

                   var event = dom.flow("1,2,3,4", function(){//这样就完全等于EventProxy 的 assignAll API
                        for (var i=0; i!=4; ++i){
                            console.log(arguments[i]);
                        }
                    });
    
                    console.log("first");
                    event.fire("1", 1);
                    event.fire("2", 2);
                    event.fire("3", 3);
                    event.fire("3", 3.5);
                    event.fire("4", 4);
                    console.log("second");
                    event.fire("1", 1);
                    event.fire("2", 2);
                    event.fire("3", 3);
                    event.fire("4", 4);
    /**
    first
    1
    2
    3.5
    4
    second
    1
    2
    3.5
    4
    1
    2
    3.5
    4
    1
    2
    3
    4
    1
    2
    3
    4
    */
    

    操作流大概能应对90%的异步操作,但面对存款取款这种事务式操作,还是很无力,我考虑是否要引进“锁”的概念。不过这是很久以后的事吧,因为一般的ORM应该能帮我们搞定这东西。介绍完毕。源码放在github上,自己去看。

  • 相关阅读:
    CompletableFuture(yet)
    模拟future
    一次使用jmap评估是否可以把类似session信息挂靠在某未知框架的某暴露对象上
    只读事务与普通读
    多重分表分库一般解决方案
    mat解决oom一般方法实践
    类加载器隔离朴实案例(二)logback
    在51系列中data,idata,xdata,pdata的区别
    linux 简单的DMA例程
    disable_irq()与disable_irq_nosync()区别
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/2258230.html
Copyright © 2020-2023  润新知