Search code examples
javascriptjquerydelaysettimeoutjquery-deferred

Alternating between nested setTimeout loops with different delays


I'm setting up a browser-based cognitive psychology experiment that's similar to the game Simon (but without the '80s flair).

Some php determines the sequence that will be displayed on a given trial and passes it to a JS array, where I'm running into two kinds of problems: (1) elegantly handling stimulus display, and (2) separating the "presentation" phase from the "recall" phase. Although I've found some helpful information on here about each half of this problem, integrating these two tasks has presented unique challenges that haven't been discussed here, to the best of my knowledge. So here goes:

(1) Displaying the contents of an array one element at a time, using setTimeouts:

This part of the code is functional, but I can't imagine that it's optimal. Part of the complication is that the time interval is uneven: the stimulus appear for 700ms, but the gap before, between, and after them is 500ms. (Annoying, but I can't help it.) Fortunately for me, there are never more than 5 things that need to be presented, so hard-coding the timestamps like that is at least an option, if not a good one.

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title></title>

    <style>
            body 
            {color:black}

            table
            {margin: 100px auto 0 auto;
             border: 1;
             text-align:center;

            }
            td
            {
            width: 100px;
            height:100px;
            vertical-align:middle;
            }
            tr:focus 
            { outline: none; }

        </style>
</head>
<body onload="displayStimuli()">

    <table>
        <tr>
            <td id="TopLeft">

            </td>
            <td id="TopMiddle">

            </td>
            <td id="TopRight">

            </td>
        </tr>
        <tr>
            <td id="MiddleLeft">

            </td>
            <td id="MiddleMiddle">

            </td>
            <td id="MiddleRight">

            </td>
        <tr>
            <td id="BottomLeft">

            </td>
            <td id="BottomMiddle">

            </td>
            <td id="BottomRight">

            </td>
        </tr>
        </tr>
    </table>
</body>

<script>

    var stimuli = new Array(1,2,3,4,"*");
    var cells = new Array("TopLeft", "TopRight", "BottomLeft", "BottomRight", "MiddleMiddle");
    var stimOn = new Array(500,1700,2900,4100, 5300) ; //this is a pretty unintelligent solution, but it works!
    var stimOff = new Array(1200,2400,3600,4800, 6000);

            function displayStimuli(){

    for (i=0; i<stimuli.length; i++)
        {

            setTimeout(function (i) {return function(){
                document.getElementById(cells[i]).innerHTML=stimuli[i];
                };
            }(i),stimOff[i]);//when display the next one

            setTimeout(function (i) {return function(){
                document.getElementById(cells[i-1]).innerHTML="";//There will be an error here because for the first item, i-1 is undefined. I can deal.
                };
            }(i),stimOn[i]);//when to remove the previous one
        }

        //showRecall(); //see part (2)!
    } 



</script>

If anyone wants to come up with a nice way to clean that up, feel free! However, my more vexing challenge (for now) is the next part.

(2) Waiting to call a function until the browser has finished displaying the end result of some previous setTimeout function.

Here I've found suggestions that might be partial solutions (e.g. Javascript nested loops with setTimeout), but I can't quite wrap my head around how to integrate them with my current design. Here's what I want to happen, conceptually:

//php sends stimulus array (not including final "*") to js
//js steps through the array, displaying and removing the pictures as above
//after the browser has finished *displaying* the sitmuli in the loop, call a new js function, "recall()".
//recall(){
//this part is fairly straightforward
//}

Some of the answers I've seen seem to suggest that this is possible with plain ol' js, while others insist that jQuery can solve the problem. I'm open to both kinds of solutions, but haven't seen any that would be suitable for my situation, where the cue for executing the function isn't a certain timeout (since it will vary across trials of varying length), nor is it just a matter of waiting for the code to be processed, but rather of detecting when the browser is done displaying stuff.

Halp?


Solution

  • This is just a suggestion / alternate way of doing things, but instead of using a for loop and calculating timeouts further in the future, you could try and set new timeouts incrementally, like this :

    var stimuli = new Array(1,2,3,4,"*");
    var cells = new Array("TopLeft", "TopRight", "BottomLeft", "BottomRight", "MiddleMiddle");
    
    var alreadyOn = false;
    
    // call startUp on load instead of displayStimuli
    function startUp() {
        setTimeout(displayStimuli, 1200);
    }
    
    function displayStimuli(){
        if(stimuli.length>0) { // if we have any stimuli left
            if(alreadyOn) {
                document.getElementById(cells.shift()).innerHTML="";
                stimuli.shift(); // here we remove elements from the arrays
    
                setTimeout(displayStimuli, 500); // show again in 500ms
            } else {
                document.getElementById(cells[0]).innerHTML=stimuli[0];
    
                setTimeout(displayStimuli, 700); // remove in 700ms
            }
            alreadyOn = !alreadyOn; // flip it for the next round
        } else { // no stimuli left, move on.
            showRecall();
        }
    }
    

    This code will call showRecall 500ms after removing the last stimulus, you could change that to your own needs, they're just if statements so just insert your own logic there.