How to wait until lines are printed iteratively?

I am building a terminal like personal website and I want to display a welcome banner + message after one another. I have this cool effect where individual lines appear from top to bottom and characters from left to right (See here for yourself).

My problem is that the welcome banner and the welcome message are mixed up with one another. Thus, I want to wait until the banner is printed before printing the message. However something about my code must be wrong… (I tried using await/async) …

Here you have a small reproducible example of the issue. All lines with the character "a" should be printed before lines with character "b". Can you please point out what the root of the problem is?

banner = ["aaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaa"]
welcomeMsg = ["bbbbbbbbbbbb", "bbbbbbbbbbbb", "bbbbbbbbbbbb", "bbbbbbbbbbbb"]
var before = document.getElementById("before");


setTimeout(async function() {
  await loopLines(banner, "", 80);
  loopLines(welcomeMsg, "", 80);
}, 100);

async function addLine(text, style, time) {
  var t = "";
  for (let i = 0; i < text.length; i++) {
    if (text.charAt(i) == " " && text.charAt(i + 1) == " ") {
      t += "&nbsp;&nbsp;";
      i++;
    } else {
      t += text.charAt(i);
    }
  }
  setTimeout(function() {
    var next = document.createElement("p");
    next.innerHTML = t;
    next.className = style;

    before.parentNode.insertBefore(next, before);

    window.scrollTo(0, document.body.offsetHeight);
  }, time);
  return;
}

async function loopLines(name, style, time) {
  for (var i = 0; i < name.length; i++) {
    await addLine(name[i], style, i * time);
  }
  return;
}
p {
  display: block;
  line-height: 1.3em;
  margin: 0;
  overflow: hidden;
  /* white-space: nowrap; */
  margin: 0;
  letter-spacing: 0.05em;
  animation: typing 0.5s steps(30, end);
  /* font-size: calc(2vw + 7px); */
  font-size: min(20px, calc(1.5vw + 7px));
}
@keyframes typing {
  from {
    width: 0;
  }
  to {
    width: 100%;
  }
}
<a id="before"></a>

>Solution :

In addLine you don’t use await. Instead you call setTimeout, but that doesn’t return a pending promise. So the promise returned by addLine resolves immediately without waiting for the timeout to complete.

To change that, make use of a promisified version of setTimeout. I have added its definition at the top of the snippet. Then see where addLine uses it instead of setTimeout:

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

banner = ["aaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaa"]
welcomeMsg = ["bbbbbbbbbbbb", "bbbbbbbbbbbb", "bbbbbbbbbbbb", "bbbbbbbbbbbb"]
var before = document.getElementById("before");


setTimeout(async function() {
  await loopLines(banner, "", 80);
  loopLines(welcomeMsg, "", 80);
}, 100);

async function addLine(text, style, time) {
  var t = "";
  for (let i = 0; i < text.length; i++) {
    if (text.charAt(i) == " " && text.charAt(i + 1) == " ") {
      t += "&nbsp;&nbsp;";
      i++;
    } else {
      t += text.charAt(i);
    }
  }
  await delay(time);  // <---------------
  var next = document.createElement("p");
  next.innerHTML = t;
  next.className = style;
  before.parentNode.insertBefore(next, before);
  window.scrollTo(0, document.body.offsetHeight);
}

async function loopLines(name, style, time) {
  for (var i = 0; i < name.length; i++) {
    await addLine(name[i], style, i * time);
  }
}
p {
  display: block;
  line-height: 1.3em;
  margin: 0;
  overflow: hidden;
  /* white-space: nowrap; */
  margin: 0;
  letter-spacing: 0.05em;
  animation: typing 0.5s steps(30, end);
  /* font-size: calc(2vw + 7px); */
  font-size: min(20px, calc(1.5vw + 7px));
}
@keyframes typing {
  from {
    width: 0;
  }
  to {
    width: 100%;
  }
}
<a id="before"></a>

Leave a Reply