Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.removeClass('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.

like image 730
RyanV Avatar asked May 08 '26 00:05

RyanV


1 Answers

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/

like image 187
Alsciende Avatar answered May 10 '26 14:05

Alsciende