Run multiple recursive Promises and break when requested

Let’s look at a simple recursive function that expresses our program at a high level

let RUNNING =
  true

const main = async (elem, color = Color ()) =>
  RUNNING
    ? delay (color, FPS)
        .then (effect (color => setElemColor (elem, color)))
        .then (color => main (elem, stepColor (color)))
    : color

We’ve done a little wishful thinking with Color, stepColor, and setElemColor (among others), let’s implement those first

const Color = (r = 128, g = 128, b = 128) =>
  ({ r, g, b })

const stepColor = ({ r, g, b }, step = 8) =>
  b < 255
    ? Color (r, g, b + step)
    : g < 255
      ? Color (r, g + step, 0)
      : r < 255
        ? Color (r + step, 0, 0)
        : Color (0, 0, 0)

const setElemColor = (elem, { r, g, b }) =>
  elem.style.backgroundColor = `rgb(${r}, ${g}, ${b})`

const c = new Color () // { r: 128, g: 128, b: 128 }
setpColor (c)          // { r: 128, g: 128, b: 136 }

Now we have a way to create colors, get the “next” color, and we can set the color of an HTML element

Lastly, we write helpers delay and effect. delay will create a Promised value that resolves in ms milliseconds. effect is used for functions which have a side effect (like setting the property of an HTML element). and FPS is just our frames-per-second constant

const delay = (x, ms) =>
  new Promise (r => setTimeout (r, ms, x))

const effect = f => x =>
  (f (x), x)

const FPS =
  1000 / 30

To run the program, just call main with an input element. Because it’s an asynchronous program, don’t forget to handle both the success and errors cases. When the program finally stops, the last used color will be output.

main (document.querySelector('#main'))
  .then (console.log, console.error)
  // => { Color r: 136, g: 8, b: 40 }

To stop the program, just set RUNNING = false at any time

// stop after 5 seconds
setTimeout (() => RUNNING = false, 5000)

Here’s a working demo

const Color = (r = 128, g = 128, b = 128) =>
  ({ r, g, b })

const stepColor = ({ r, g, b }, step = 16) =>
  b < 255
    ? Color (r, g, b + step)
    : g < 255
      ? Color (r, g + step, 0)
      : r < 255
        ? Color (r + step, 0, 0)
        : Color (0, 0, 0)

const setElemColor = (elem, { r, g, b }) =>
  elem.style.backgroundColor = `rgba(${r}, ${g}, ${b}, 1)`

const delay = (x, ms) =>
  new Promise (r => setTimeout (r, ms, x))

const effect = f => x =>
  (f (x), x)

const FPS =
  1000 / 60
 
let RUNNING =
  true
  
const main = async (elem, color = Color ()) =>
  RUNNING
    ? delay (color, FPS)
        .then (effect (color => setElemColor (elem, color)))
        .then (color => main (elem, stepColor (color)))
    : color

main (document.querySelector('#main'))
  .then (console.log, console.error)
  // => { r: 136, g: 8, b: 40 }
  
// stop after 5 seconds
setTimeout (() => RUNNING = false, 5000)
#main {
  width: 100px;
  height: 100px;
  background-color: rgb(128, 128, 128);
}
<div id="main"></div>
<p>runs for 5 seconds...</p>

Leave a Comment