Search code examples
javascriptfunctionloopsnestedcall

How do I pause and call a function inside a nested for loop?


I'm trying to help a student who wants to display the sorting of an array of randomly-generated colours on a webpage. He has a function that fills a global array with randomly-generated HSL colours:

function shuffle(array)
{
  for(i=array.length-1;i>0;i--)
  {
    var random = Math.floor(Math.random() * (i+1));
    var temp = array[i];
    array[i] = array[random];
    array[random] = temp;
  }
}

And another function that displays on an HTML canvas a series of thin, vertical rectangles of the colours in the array from left to right:

function draw()
{ 
  for(d=0;d<361;d++)
  {
    hue = cArray[d].slice(4,cArray[d].indexOf(",", 4));
    ctx.fillStyle = `hsl(`+ hue + `,100%,50%)`;
    ctx.fillRect(x,0,4,canvas.height);
    x=x+3;
  }
  x=0; //reset x-coordinate
}

And then a function containing a bubble sort algorithm:

function bubbleSort(array)
{
  for(i=0;i<array.length;i++)
  {
    for(j=1;j<array.length;j++)
    {
      var hue1 = array[j-1].slice(4,array[j-1].indexOf(","));
      var hue2 = array[j].slice(4,array[j].indexOf(","));
      if(Number(hue1) > Number(hue2))
      {
        var temp = array[j-1];
        array[j-1] = array[j];
        array[j] = temp;
        // webpage hangs if function call put here
      }
    }
  }
  // webpage works if function call put here
}

If the draw() function call is inside the nested loop, the webpage hangs. But if the draw() function call is after the loop, the sorting is finished and the student only sees the final sorted array. How can we pause the loop and call the draw() function at intervals so that the sorting algorithm displays the process?

I've looked at a lot of discussion forums that suggest asynchronous and recursive solutions (that I can only make some sense of) but they never seem to apply to nested loops. Any thoughts?


Solution

  • The simplest way is to make the functions async (an ES2017 thing, which you can transpile if you have to target older environments). Then, you can await a delay that allows the browser to repaint the canvas. Using an async function lets you keep writing loops rather than asynchronous callbacks, while still getting the async behavior you need to display things on the canvas.

    Here's a simpler example:

    const outer = document.getElementById("outer");
    const inner = document.getElementById("inner");
    
    function delay(ms) {
        return new Promise(resolve => {
            setTimeout(() => {
                resolve();
            }, ms);
        });
    }
    
    async function example() {
        for (let i = 0; i < 4; ++i) {
          outer.textContent = String(i);
          outer.className = "cls" + (i % 4);
          for (let j = 0; j < 4; ++j) {
              inner.textContent = String(j);
              inner.className = "cls" + (j % 4);
              await delay(200);
          }
        }
    }
    
    example()
    .catch(error => {
        console.error(error);
    });
    .cls0 {
      color: blue;
    }
    .cls1 {
      color: red;
    }
    .cls2 {
      color: green;
    }
    .cls3 {
      color: black;
    }
    <div id="outer"></div>
    <div id="inner"></div>


    The code in the question is falling prey to what I call (on my anemic little blog) The Horror of Implicit Globals. Your student needs to declare those loop counters.