一种模仿线程的Javascript异步模型设计&实现

系统 2345 0

jQuery中所支持的异步模型为:

  • Callbacks,回调函数列队。
  • Deferred,延迟执行对象。
  • Promise,是Deferred只暴露非状态改变方法的对象。

这些模型都很漂亮,但我想要一种更帅气的异步模型。

 

Thread?

我们知道链式操作是可以很好的表征运行顺序的(可以参考我的文章《 jQuery链式操作 》),然而通常基于回调函数或者基于事件监听的异步模型中,代码的执行顺序不清晰。

Callbacks模型实际上类似一个自定义事件的回调函数队列,当触发该事件(调用Callbacks.fire())时,则回调队列中的所有回调函数。

Deferred是个延迟执行对象,可以注册Deferred成功、失败或进行中状态的回调函数,然后通过触发相应的事件来回调函数。

这两种异步模型都类似于事件监听异步模型,实质上顺序依然是分离的。

当然Promise看似能提供我需要的东西,比如Promise.then().then().then()。但是,Promise虽然成功用链式操作明确了异步编程的顺序执行,但是没有循环,成功和失败分支是通过内部代码确定的。

个人认为,Promise是为了规范化后端nodejs中I/O操作异步模型的,因为I/O状态只有成功和失败两种状态,所以他是非常成功的。

但在前端,要么只有成功根本没有失败,要么不止只有两种状态,不应当固定只提供三种状态的方案,我觉得应该提供可表征多状态的异步方案。

这个大家可以在something more看到。

我想要一种类似于线程的模型,我们在这里称为Thread,也就是他能顺序执行、也能循环执行、当然还有分支执行。

 

顺序执行

线程的顺序执行流程,也就是类似于:

      
        do1();
do2();
do3();
      
    

这样就是依次执行do1,do2,do3。因为这是异步模型,所以我们希望能添加wait方法,即类似于:

      
        do1();
wait(
      
      1000);    
      
        //
      
      
        等待1000ms
      
      
        do2();
wait(
      
      1000);    
      
        //
      
      
        等待1000ms
      
      
        do3();
wait(
      
      1000);    
      
        //
      
      
        等待1000ms
      
    

不使用编译方法的话,使用链式操作来表征顺序,则实现后的样子应当是这样的:

      Thread().    
      
        //
      
      
        获取线程
      
      
then(do1).    
      
        //
      
      
        然后执行do1
      
      
wait(1000).    
      
        //
      
      
        等待1000ms
      
      
then(do2).    
      
        //
      
      
        然后执行do2
      
      
wait(1000).    
      
        //
      
      
        等待1000ms
      
      
then(do3).    
      
        //
      
      
        然后执行do3
      
      
wait(1000);    
      
        //
      
      
        等待1000ms
      
    

 

循环执行

循环这很好理解,比如for循环:

      
        for
      
      (; 
      
        true
      
      
        ;){
    
      
      
        dosomething
      
      
        ();
    wait(1000);
}
      
    

进行无限次循环执行do,并且每次都延迟1000ms。则其链式表达应当是这样的:

      Thread().    
      
        //
      
      
        获取线程
      
      
loop(
      
        -1
      
      ).    
      
        //
      
      
        循环开始,正数则表示循环正数次,负数则表示循环无限次
      
      
    then(
      
        dosomething
      
      ).    
      
        //
      
      
        然后执行do
      
      
    wait(1000).    
      
        //
      
      
        等待1000ms
      
      
loopEnd();    
      
        //
      
      
        循环结束
      
    

这个可以参考后面的例子。 

 

分支执行

分支也就是if...else,比如:

      
        if
      
      (
      
        true
      
      
        ){
    doSccess();
}
      
      
        else
      
      
        {
    doFail();
}
      
    

那么其链式实现应当是:

      Thread().    
      
        //
      
      
        获得线程
      
      
right(
      
        true
      
      ).    
      
        //
      
      
        如果表达式正确
      
      
    then(doSccess).    
      
        //
      
      
        执行doSccess
      
      
left().    
      
        //
      
      
        否则
      
      
    then(doFail).    
      
        //
      
      
        执行doFail
      
      
leftEnd().    
      
        //
      
      
        left分支结束
      
      
rightEnd();    
      
        //
      
      
        right分支结束
      
    

 

声明变量

声明变量也就是:

      
        var
      
       a = "hello world!";
    

可被其它函数使用。那么我们的实现是:

      Thread().    
      
        //
      
      
        得到线程
      
      
define("hello world!").    
      
        //
      
      
        将回调函数第一个参数设为hello world!
      
      
then(
      
        function
      
      (a){alert(a);});    
      
        //
      
      
        获取变量a,alert出来
      
    

 

顺序执行实现方案

一种模仿线程的Javascript异步模型设计&实现_第1张图片 Thread实际上是一个打包函数Fn队列。

所谓打包函数就是将回调函数打包后产生的新的函数,举个例子:

      
        function package(callback){
    return function(){
        callback();
        // 干其他事情
    }
}
      
    

这样我们就将callback函数打包起来了。

Thread提供一个fire方法来触发线程取出一个打包函数然后执行,打包函数执行以后回调Thread的fire方法。

一种模仿线程的Javascript异步模型设计&实现_第2张图片

那么我们就可以顺序执行函数了。

现在只要打包的时候设置setTimeout执行,则这个线程就能实现wait方法了。

 

循环执行实现方案

循环Loop是一个Thread的变形,只不过在执行里面的打包函数的时候使用另外一种方案,通过添加一个指针取出,执行完后触发Loop继续,移动指针取出下一个打包函数。

一种模仿线程的Javascript异步模型设计&实现_第3张图片

 

分支执行实现方案

分支Right和Left也是Thread的一种变形,开启分支的时候,主Thread会创建两个分支Right线程和Left线程,打包一个触发分支Thread的函数推入队列,然后当执行到该函数的时候判断触发哪个分支执行。

一种模仿线程的Javascript异步模型设计&实现_第4张图片

其中一个队列执行结束后回调主Thread,通知进行下一步。 

 

例子

由于该方案和wind-asycn非常相似,所以我们拿wind.js中的clock例子进行改造看看其中的差别吧。

wind.js中的例子:

      <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
    <title>Clock - Wind.js Sample</title>
    <meta http-equiv="X-UA-Compatible" content="IE=9" />    
    
    <script src="http://www.cnblogs.com/../src/wind-core.js"></script>
    <script src="http://www.cnblogs.com/../src/wind-compiler.js"></script>
    <script src="http://www.cnblogs.com/../src/wind-builderbase.js"></script>
    <script src="http://www.cnblogs.com/../src/wind-async.js"></script>

    <script>
        var drawHand = function(value, length) {
            ctx.beginPath();
            
            var angle = Math.PI * 2 * value / 60;
            var x = Math.sin(angle) * length;
            var y = Math.cos(angle) * length;
            
            ctx.moveTo(100, 100);
            ctx.lineTo(100 + x, 100 - y);
            ctx.stroke();
        }
    
        var drawClock = function(time) {
            if (!ctx) {
                var h = time.getHours();
                var m = time.getMinutes();
                var s = time.getSeconds();
                
                var text = 
                    ((h >= 10) ? h : "0" + h) + ":" +
                    ((h >= 10) ? m : "0" + m) + ":" +
                    ((h >= 10) ? s : "0" + s);
                
                document.getElementById("clockText").innerHTML = text;
                return;
            }
        
            ctx.clearRect(0, 0, 200, 200);
            
            ctx.beginPath();
            ctx.arc(100, 100, 90, 0, Math.PI * 2, false);
            for (var i = 0; i < 60; i += 5) {
                var angle = Math.PI * 2 * i / 60;
                var x = Math.sin(angle);
                var y = Math.cos(angle);
                ctx.moveTo(100 + x * 85, 100 - y * 85);
                ctx.lineTo(100 + x * 90, 100 - y * 90);
            }
            ctx.stroke();
            
            drawHand(time.getSeconds(), 80);
            drawHand(time.getMinutes() + time.getSeconds() * 1.0 / 60, 60);
            drawHand(time.getHours() % 12 * 5 + time.getMinutes() * 1.0 / 12, 40);
        }
    
        var drawClockAsync = eval(Wind.compile("async", function(interval) {
            while (true) {
                drawClock(new Date());
                $await(Wind.Async.sleep(interval));
            }
        }));
    </script>
</head>
<body>
    <canvas id="clockCanvas" height="200" width="200">
        <div id="clockText" style="font-size:20pt;"></div>
    </canvas>
    <script>
        var canvas = document.getElementById("clockCanvas");
        var ctx = canvas.getContext ? canvas.getContext("2d") : null;
        drawClockAsync(1000).start();
    </script>
</body>
</html>

    

我的例子:

      <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
    <title>Clock - asThread.js Sample</title>
    <meta http-equiv="X-UA-Compatible" content="IE=9" />    
    <!-- 例子修改自wind.js -->
    <script src="asThread.js"></script>

    <script>
        var drawHand = function(value, length) {
            ctx.beginPath();
            
            var angle = Math.PI * 2 * value / 60;
            var x = Math.sin(angle) * length;
            var y = Math.cos(angle) * length;
            
            ctx.moveTo(100, 100);
            ctx.lineTo(100 + x, 100 - y);
            ctx.stroke();
        }
    
        var drawClock = function() {
            var time = new Date()
            if (!ctx) {
                var h = time.getHours();
                var m = time.getMinutes();
                var s = time.getSeconds();
                
                var text = 
                    ((h >= 10) ? h : "0" + h) + ":" +
                    ((h >= 10) ? m : "0" + m) + ":" +
                    ((h >= 10) ? s : "0" + s);
                
                document.getElementById("clockText").innerHTML = text;
                return;
            }
        
            ctx.clearRect(0, 0, 200, 200);
            
            ctx.beginPath();
            ctx.arc(100, 100, 90, 0, Math.PI * 2, false);
            for (var i = 0; i < 60; i += 5) {
                var angle = Math.PI * 2 * i / 60;
                var x = Math.sin(angle);
                var y = Math.cos(angle);
                ctx.moveTo(100 + x * 85, 100 - y * 85);
                ctx.lineTo(100 + x * 90, 100 - y * 90);
            }
            ctx.stroke();
            
            drawHand(time.getSeconds(), 80);
            drawHand(time.getMinutes() + time.getSeconds() * 1.0 / 60, 60);
            drawHand(time.getHours() % 12 * 5 + time.getMinutes() * 1.0 / 12, 40);
            
        }
        
        Thread().    // 使用主线程线程
        loop(-1).    // 负数表示循环无限多次,如果是正数n,则表示n次循环
            then(drawClock). // 循环中运行drawClock
            wait(1000).    // 然后等待1000ms
        loopEnd();    // 循环结束
        // 线程定义结束
    </script>
</head>
<body>
    <canvas id="clockCanvas" height="200" width="200">
        <div id="clockText" style="font-size:20pt;"></div>
    </canvas>
    <script>
        var canvas = document.getElementById("clockCanvas");
        var ctx = canvas.getContext ? canvas.getContext("2d") : null;
        Thread().run();    // 运行线程
    </script>
</body>
</html>

    

Something more?

  • 将事件当成分支处理

我们提供了on方法将事件转成分支来执行。

举个例子页面有个按钮“点我”,但是我们希望打开页面5秒内单击没有效,5秒后显示“请点击按钮”后,单击才会出现“你成功点击了”。

使用on分支是这样的:

        
          <!
        
        
          DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
        
        
          >
        
        
          <
        
        
          html
        
        
          >
        
        
          <
        
        
          head
        
        
          >
        
        
          <
        
        
          title
        
        
          >
        
        on - asThread.js Sample
        
          </
        
        
          title
        
        
          >
        
        
          <
        
        
          meta 
        
        
          http-equiv
        
        
          ="X-UA-Compatible"
        
        
           content
        
        
          ="IE=9"
        
        
          />
        
        
          <
        
        
          script 
        
        
          src
        
        
          ="asThread.js"
        
        
          ></
        
        
          script
        
        
          >
        
        
          </
        
        
          head
        
        
          >
        
        
          <
        
        
          body
        
        
          >
        
        
          <
        
        
          button 
        
        
          id 
        
        
          = "b"
        
        
          >
        
        点我
        
          </
        
        
          button
        
        
          >
        
        
          <
        
        
          script
        
        
          >
        
        
          var
        
        
           ele 
        
        
          =
        
        
           document.getElementById(
        
        
          "
        
        
          b
        
        
          "
        
        
          );
    
        Thread().    
        
        
          //
        
        
           获得线程
        
        
                  then(
        
        
          function
        
        
          (){alert(
        
        
          "
        
        
          请点击按钮
        
        
          "
        
        
          )}, 
        
        
          5000
        
        
          ).    
        
        
          //
        
        
          然后等5秒显示"请点击按钮"
        
        
                  on(ele, 
        
        
          "
        
        
          click
        
        
          "
        
        
          ).    
        
        
          //
        
        
           事件分支On开始,如果ele触发了click事件
        
        
                      then(
        
        
          function
        
        
          (){alert(
        
        
          "
        
        
          你成功点击了
        
        
          "
        
        
          )}).    
        
        
          //
        
        
          那么执行你成功点击了
        
        
                  onEnd().    
        
        
          //
        
        
           事件分支On结束
        
        
                  then(
        
        
          function
        
        
          (){alert(
        
        
          "
        
        
          都说可以的了
        
        
          "
        
        
          )}).    
        
        
          //
        
        
           然后弹出"都说可以的了"
        
        
                  run();    
        
        
          //
        
        
          启动线程
        
        
          </
        
        
          script
        
        
          >
        
        
          </
        
        
          body
        
        
          >
        
        
          </
        
        
          html
        
        
          >
        
      

自定义事件也可以哦,只要在.on时候传进去注册监听函数,和删除监听函数就行了。比如:

        
          function
        
        
           addEvent(__elem, __type, __handler){
    
        
        
          //
        
        
          添加监听
        
        
          }


        
        
          function
        
        
           removeEvent(__elem, __type, __handler){
    
        
        
          //
        
        
          删除监听
        
        
          }

Thread().
on(ele, 
        
        "success"
        
          , addEvent, removeEvent).
    then(
        
        
          function
        
        (){alert("成功!"
        
          )}).
onEnd().
run();
        
      

当然实际上我们还可以注册多个事件分支。事件分支是并列的,也就是平级的事件分支没有现有顺序,所以我们能这样:

        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
    <title>on - asThread.js Sample</title>
    <meta http-equiv="X-UA-Compatible" content="IE=9" />    
    <script src="asThread.js"></script>
</head>
<body>
    <button id = "b">点我</button>
    <button id = "c">点我</button>
    <script>
        
        
          var
        
         ele0 = document.getElementById("b"
        
          ),
              ele1 
        
        = document.getElementById("c"
        
          );
    
        Thread().    
        
        
          //
        
        
           获得线程
        
        
        then(
        
          function
        
        (){alert("请点击按钮")}, 5000).    
        
          //
        
        
          然后等5秒显示"请点击按钮"
        
        
        on(ele0, "click").    
        
          //
        
        
           事件分支On开始,如果ele0触发了click事件
        
        
            then(
        
          function
        
        (){alert("你成功点击了")}).    
        
          //
        
        
          那么执行你成功点击了
        
        
        on(ele1, "click").    
        
          //
        
        
           事件分支On开始,如果ele1触发了click事件
        
        
            then(
        
          function
        
        (){alert("你成功点击了")}).    
        
          //
        
        
          那么执行你成功点击了
        
        
        onEnd().    
        
          //
        
        
           事件分支On结束
        
        
        then(
        
          function
        
        (){alert("都说可以的了")}).    
        
          //
        
        
           然后弹出"都说可以的了"
        
        
        run();    
        
          //
        
        
          启动线程
        
        
    </script>
</body>
</html>    
      
  • 开辟多个线程

一个线程不够用?只要输入名字就能开辟或者得到线程了。

系统会自动初始化一个主线程,当不传参数时就直接返回主线程:

        Thread() 
        
          //
        
        
          得到主线程
        
      

但如果主线程正在用想开辟一个线程时,只要给个名字就行,比如:

        Thread("hello")    
        
          //
        
        
          得到名字是hello的线程
        
      

那么下次再想用该线程时只要输入相同的名字就行了:

        Thread("hello")    
        
          //
        
        
          得到hello线程
        
      

默认只最多只提供10个线程,所以用完记得删掉:

        Thread("hello").del();
      
  • setImmediate

IE10已经提供了setImmediate方法,而其他现代浏览器也可以模拟该方法,其原理是推倒线程末端,使得浏览器画面能渲染,得到比setTimeout(0)更快的响应。

我们通过接口.imm来提供这一功能。比如:

        
          Thread().
imm(
        
        
          function
        
        (){alert("hello world"
        
          )}).
run();
        
      

这方法和.then(fn)不太一样,.then(fn)是可能阻塞当前浏览器线程的,但.imm(fn)是将处理推到浏览器引擎列队末端,排到队了在运行。

所以如果你使用多个Thread(伪多线程),而又希望确保线程是并行运行的,那么请使用.imm来替代.then。

当然对于老版IE,只能用setTimeout(0)替代了。

  • 分支参数可以是函数

分支Right传的参数如果只是布尔值肯定很不爽,因为这意味着分支是静态的,在初始化时候就决定了,但我们希望分支能在执行到的时候再判断是走Right还是Left,所以我们提供了传参可以是函数(但是函数返回值需要是布尔值,否则……╮(╯▽╰)╭也会转成布尔值的……哈哈)。比如:

        fucntion foo(
        
          boolean
        
        
          ){
    
        
        
          return
        
         !
        
          boolean
        
        
          ;
}

Thread().
define(
        
        
          true
        
        
          ).
right(foo).
    then(
        
        
          function
        
        (){
        
          /*
        
        
          这里不会运行到*/}).
        
        
          rightEnd().
run();
        
      

Enjoy yourself!!

 

项目地址

https://github.com/miniflycn/asThread

 

一种模仿线程的Javascript异步模型设计&实现


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论