开发者

How do I prevent the concurrent execution of a javascript function?

I am making a ticker similar to the "From the AP" one at The Huffington Post, using jQuery. The ticker rotates through a ul, either by user command (clicking an arrow) or by an auto-scroll.

Each list-item is display:none by default. It is revealed by the addition of a "showHeadline" class which is display:list-item. HTML for the UL Looks like this:

        <ul class="news" id="news">
            <li class="tickerTitle showHeadline">Test Entry</li>
            <li class="tickerTitle">Test Entry2</li>
            <li class="tickerTitle">Test Entry3</li>
        </ul>

When the user clicks the right arrow, or the auto-scroll setTimeout goes off, it runs a tickForward() function:

function tickForward(){
  var $active = $('#news li.showHeadline');
  var $next = $active.next();
  if($next.length==0) $next = $('#news li:first');
  $active.stop(true, true);
  $active.fadeOut('slow', function() {$active.removeC开发者_如何学Pythonlass('showHeadline');});

  setTimeout(function(){$next.fadeIn('slow', function(){$next.addClass('showHeadline');})}, 1000);
  if(isPaused == true){
  }
  else{
          startScroll()
  }
     };

This is heavily inspired by Jon Raasch's A Simple jQuery Slideshow. Basically, find what's visible, what should be visible next, make the visible thing fade and remove the class that marks it as visible, then fade in the next thing and add the class that makes it visible.

Now, everything is hunky-dory if the auto-scroll is running, kicking off tickForward() once every three seconds. But if the user clicks the arrow button repeatedly, it creates two negative conditions:

  1. Rather than advance quickly through the list for just the number of clicks made, it continues scrolling at a faster-than-normal rate indefinitely.
  2. It can produce a situation where two (or more) list items are given the .showHeadline class, so there's overlap on the list.

I can see these happening (especially #2) because the tickForward() function can run concurrently with itself, producing different sets of $active and $next.

So I think my question is: What would be the best way to prevent concurrent execution of the tickForward() method?

Some things I have tried or considered:

  • Setting a Flag: When tickForward() runs, it sets an isRunning flag to true, and sets it back to false right before it ends. The logic for the event handler is set to only call tickForward() if isRunning is false. I tried a simple implementation of this, and isRunning never appeared to be changed.

  • The jQuery queue(): I think it would be useful to queue up the tickForward() commands, so if you clicked it five times quickly, it would still run as commanded but wouldn't run concurrently. However, in my cursory reading on the subject, it appears that a queue has to be attached to the object its queue applies to, and since my tickForward() method affects multiple lis, I don't know where I'd attach it.


You can't have concurrent executions of a function in javascript. You just have several calls waiting to execute in order on the pile of execution. So setting a flag when the function runs cannot work. When the event handler runs, it cannot run concurrently with a tickHandler() execution (javascript is threadless).

Now you have to define precisely what you want, because it doesn't appear in your question. What you happen when the user clicks, say, 3 times in rapid succession on the arrow? And how do the clicks interfere with the auto-scroll?

I'd say the easiest way would be to process a click only when the ticker is idle, so 3 clicks in a row will only tick once. And you make clicks replace auto-scroll and reset its timer. So I use a flag ticksInQueue that is raised when a tick is queue by a click and only lowered when the fadeIn has completed:

    var ticksInQueue = 0,
        timerId = setInterval(tickForward, 5000),
        isPaused = false;

    function tickForward() {
        var $active = $('#news li.showHeadline');
        var $next = $active.next();
        if($next.length==0) $next = $('#news li:first');
        $active.stop(true, true);

        $active.fadeOut('slow', function() {
            $active.removeClass('showHeadline');
        });

        setTimeout(function(){
                $next.fadeIn('slow', function(){
                    $next.addClass('showHeadline');
                    if(ticksInQueue) ticksInQueue--; // only change
                })}, 1000);

        if(isPaused == true){
        } else {
            startScroll()
        }
   }

    $('#arrow').click(function () {
        if(ticksInQueue) return;
        ticksInQueue++;
        clearInterval(timerId);
        timerId = setInterval(tickForward, 5000);
        tickForward();
    });

You can try a demo here : http://jsfiddle.net/mhaCF/


You can set up a bool variable and check it's value when the function executes. If it's already being run you can either wait for it to end or just skip it (don't know which is desirable)

var active = false;
function tickForward() {
    if (!active) {
        active = true;

        // your function code goes here

        // remember to change active to false once you're done
        active = false;
    } else {
        // if you want to skip if active = true you don't need this else
        // if you want to call your method later use something like this
        setTimeout(tickForward, 1000);
    }
}


This implementation using "new Date()" and "setTimeout" to avoid some race condition (concurrent execution) for some resource. That way each call to "NoConcurrExecFunc" is executed with at least 750 milliseconds between each one!

var last_call = new Date();
function NoConcurrExecFunc(param_a, param_b) {
    var new_date = new Date();
    var min_interval = 750;
    if ((last_call - new_date) < 0) {
        last_call = new Date(new_date.getTime() + min_interval);


        // Do your stuff!


    } else {
        setTimeout(function () {
            NoConcurrExecFunc(param_a, param_b);
        }, min_interval);
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜