Search code examples
javascriptrecursionasync-awaitconsolebatch-processing

Recursion in console, batch calculation (JS)


I have a website which calculates various aspects of musical intonation. I would like to do a batch calculation in the console, but it seems that recursion is causing a problem because it needs to run a function (maybe?). I have a very large array called ratio which holds many numerators and denominators. I would like to inject them into pre-existing variables called inputNum and inputDen and then run the underlying JS function doCalc() that I have coded on the site, which takes the inputs and does various calculations with them. The result outputs into some div fields on the page, which I just want to print in the log (to copy and paste into a spreadsheet). My code looks like this so far, after defining the large array of fractions ratio.

var i = 0;                  

async function loop() {   
    inputNum = ratio[i][0];
    inputDen = ratio[i][1];
    await doCalc();       
    console.log(document.getElementById("notationOutput").innerHTML+" "+document.getElementById("noteName").innerHTML);   
    i++;                   
    if (i < ratio.length) {           
        loop();             
    }                      
}

loop(); 

Since it takes a non-negligible amount of time to do the calculation before displaying the results (I assume), I thought an async function would be the way to go, but I don't seem to be implementing it correctly. Otherwise, I would have done it with a simple while loop, but apparently setTimeout won't work in that case.


Solution

  • As discussed in the comments, what you want to do should be possible, but it's a pretty awful way of writing this sort of functionality. Depending upon the DOM and global variables to do your work is pretty nasty.

    If we start with something like this:

    const doCalc = () => {
      const num = Number (document .getElementById ('numerator') .value)
      const den = Number (document .getElementById ('denominator') .value)
      // do some real work here
      const notationOutput = `${num}/${den}`
      const noteName = `${num}--${den}`
      document .getElementById ("notationOutput") .innerHTML = notationOutput
      document .getElementById ("noteName") .innerHTML = noteName
    }
    
    document .getElementById ('numerator') .onchange = doCalc
    document .getElementById ('denominator') .onchange = doCalc
    
    const logAll = (ratios) =>
      ratios 
        .forEach (([num, den]) => {
           document .getElementById ('numerator') .value = String (num)
           document .getElementById ('denominator') .value = String (den)
           doCalc()
           const notationOutput = document .getElementById ("notationOutput") .innerHTML
           const noteName = document .getElementById ("noteName") .innerHTML
           console.log(`${notationOutput} ${noteName}`)
        })
    
    const ratios = [[5, 3], [7, 4], [3, 2], [13, 1]]
    
    document.getElementById('batch') .onclick = (evt) => logAll (ratios)
    <p>
      <label>Numerator: <input id="numerator" /></label>
      <label>Denominator: <input id="denominator" /></lbael>
    </p>
    <p>Notation Output <span id="notationOutput"></span></p>
    <p>Note Name <span id="noteName"></span></p>
    <p><button id="batch">Run Batch</button></p>


    We would eventually want to get to something like this:

    const doCalc = ([num, den]) => {
      // do some real work here
      return {
        notationOutput: `${num}/${den}`,
        noteName: `${num}--${den}`
      }
    }
    
    const change = (evt) => {
      const num = Number (document .getElementById ('numerator') .value)
      const den = Number (document .getElementById ('denominator') .value)
      const {notationOutput, noteName} = doCalc ([num, den])
      document .getElementById ("notationOutput") .innerHTML = notationOutput
      document .getElementById ("noteName") .innerHTML = noteName
    }
    
    document .getElementById ('numerator') .onchange = change
    document .getElementById ('denominator') .onchange = change
    
    const logAll = (ratios) =>
      ratios 
        .map (doCalc) 
        .forEach (
          ({notationOutput, noteName}) => console.log(`${notationOutput} ${noteName}`)
        )
    
    const ratios = [[5, 3], [7, 4], [3, 2], [13, 1]]
    
    document.getElementById('batch') .onclick = (evt) => logAll (ratios)
    <p>
      <label>Numerator: <input id="numerator" /></label>
      <label>Denominator: <input id="denominator" /></lbael>
    </p>
    <p>Notation Output <span id="notationOutput"></span></p>
    <p>Note Name <span id="noteName"></span></p>
    <p><button id="batch">Run Batch</button></p>

    The big difference is that the calculation is done without worrying about the DOM. It is a pure function, and the code that uses it can use it to write to the console or to update the DOM.


    Note also, though, that my first block does work. It's possible that if you changed your loop to this sort of structure that it will work for you. But it would then forever remain code difficult to test, difficult to extend, and difficult to understand.

    The second format is much simpler.