Home > Software engineering >  setTimeout in a for loop changes final output; how to delay runs of a for loop, without changing fin
setTimeout in a for loop changes final output; how to delay runs of a for loop, without changing fin

Time:09-17

I'm trying to make a custom typewriter effect on my site using a javascript function. In order to add a delay between the typing of each letter, I've implemented a staggered setTimeout for each run of the for loop, so each iteration is delayed by an extra 300ms compared to the previous one. I need special characters (' ' '<', '&gt') to be typed as single characters rather than as individual letters, so I have a few else if conditions. This produces the desired effect when setTimeout is excluded. However, adding the delay results in spelling out the individual letters of this code after printing out the whole character. How can I fix this?

function type(fullTxt, current = '') {
    for (let i = current.length, j = 0; i < fullTxt.length; i  , j  ) {
         setTimeout(function() {
            if (fullTxt.substring(i, i 6) === '&nbsp;') {
                console.log(fullTxt.substring(0, i 6));
                i = i   5;
            } else if (fullTxt.substring(i, i 4) === '&lt;') {
                console.log(fullTxt.substring(0, i 4));
                i = i   3;
            } else if (fullTxt.substring(i, i 3) === '&gt') {
                console.log(fullTxt.substring(0, i 3));
                i = i   2;
            } else {
                console.log(fullTxt.substring(0, i 1));
            }
            console.log(j); //included just to ensure j is counting properly
            console.log(i); //included to check progress of loop
            console.log(fullTxt.length); //included to check endpoint of for loop
         }, j * 300);
    }
}

type('hello&nbsp;');

Expected output:

// h
// 0
// 0
// 11
// he
// 1
// 1
// 11
// hel
// 2
// 2
// 11
// hell
// 3
// 3
// 11
// hello
// 4
// 4
// 11
// hello&nbsp;
// 5
// 10
// 11

CodePudding user response:

You could put the setTimeout on only the console.log rather than around the entire if statement.

function type(fullTxt, current = '') {
  for (let i = current.length, j = 0; i < fullTxt.length; i  , j  ) {
    if (fullTxt.substring(i, i   6) === '&nbsp;') {
      setTimeout(function() {
        console.log(fullTxt.substring(0, i   6));
      }, j * 300);
      i = i   5;
    } else if (fullTxt.substring(i, i   4) === '&lt;') {
      setTimeout(function() {
        console.log(fullTxt.substring(0, i   4));
      }, j * 300);
      i = i   3;
    } else if (fullTxt.substring(i, i   3) === '&gt') {
      setTimeout(function() {
        console.log(fullTxt.substring(0, i   3));
      }, j * 300);
      i = i   2;
    } else {
      setTimeout(function() {
        console.log(fullTxt.substring(0, i   1));
      }, j * 300);
    }
    setTimeout(function() {
      console.log(j); //included just to ensure j is counting properly
      console.log(i); //included to check progress of loop
      console.log(fullTxt.length); //included to check endpoint of for loop
    }, j * 300);
  }
}

type('hello&nbsp;');

CodePudding user response:

you can achieve this using async function

 async function type(fullTxt, current = '') {
        for (let i = current.length, j = 0; i < fullTxt.length; i  , j  ) {
             await waitFor(j*300);
                if (fullTxt.substring(i, i 6) === '&nbsp;') {
                    console.log(fullTxt.substring(0, i 6));
                    i = i   5;
                } else if (fullTxt.substring(i, i 4) === '&lt;') {
                    console.log(fullTxt.substring(0, i 4));
                    i = i   3;
                } else if (fullTxt.substring(i, i 3) === '&gt') {
                    console.log(fullTxt.substring(0, i 3));
                    i = i   2;
                } else {
                    console.log(fullTxt.substring(0, i 1));
                }
                console.log(j); //included just to ensure j is counting properly
                console.log(i); //included to check progress of loop
                console.log(fullTxt.length); //included to check endpoint of for loop
             }
        }
    


type('hello&nbsp;');

here is waitFor function

function waitFor(ms){
  return new Promise(resolve=> setTimeout(resolve,ms))
}

CodePudding user response:

The safer now is to use async/await, calling a Promise, this way, your loop is actually waiting for the one before to complete.

Also this makes stuffs easy to write, or later modify.

Timeout set to 2000 ms for the demo.

async function type(fullTxt, current = '') {
    for (let i = current.length, j = 0; i < fullTxt.length; i  , j  ) {
            await wait(2000) // Wait for 2000ms
            if (fullTxt.substring(i, i 6) === '&nbsp;') {
                console.log(fullTxt.substring(0, i 6));
                i = i   5;
            } else if (fullTxt.substring(i, i 4) === '&lt;') {
                console.log(fullTxt.substring(0, i 4));
                i = i   3;
            } else if (fullTxt.substring(i, i 3) === '&gt') {
                console.log(fullTxt.substring(0, i 3));
                i = i   2;
            } else {
                console.log(fullTxt.substring(0, i 1));
            }
            console.log(j); //included just to ensure j is counting properly
            console.log(i); //included to check progress of loop
            console.log(fullTxt.length); //included to check endpoint of for loop       
    }
}

// Generic Promise function
function wait(delayInMS) {
  return new Promise((resolve) => setTimeout(resolve, delayInMS));
}

type('hello&nbsp;');

  • Related