Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Executing a function after an another (asynchronous) function finishes in JavaScript

Please give me a vanilla JS solution as I am new to coding and introducing libraries would just confuse me more.

I have two functions in the program: changeText contains asynchronous setTimeout functions that fade text in and out at X seconds and userNameinput that allows the user to enter in a text input and then displays the input back on the browser.

The problem I am running into is that the usernameinput is executing along with the changeText function.My goal is to have the changeText function execute first and finish and then have the userNameInput (the text input line appear)execute right after.

As you can see in my code I have implemented a callback in an attempt to solve this issue. I create a new function called welcome to bundle the changeText and useNameInput functions together in such a way that when welcome is invoked it would execute changeText first, finish and then evoke userNameInput packaged in the callback. Somehow I believe that since the setTimeout functions in the changeText functions are put in queue outside of the Javascript environment for a X amount of time, JS is seeing that there is nothing there in the stack and contiunes to execute usernameInput without waiting. Please help! Been stuck for way too long! Thanks in advance.

HTML:

<div id="h1">Hello,<br></div>
    <div id="inputDiv"></div>

CSS:

 #h1{
      opacity: 0;
      transition: 1s;
}

JS:

function fadeIn() {
  document.getElementById('h1').style.opacity = '1';
}

function fadeOut() {
  document.getElementById('h1').style.opacity = '0';
}

var dialogue = ['Hello,', 'My name is Jane.', 'I have a dog!', 'What is your name?'];

var input = document.createElement("input");
input.setAttribute("type", "text");
input.setAttribute("value", "");
input.setAttribute("placeholder", "Type your name then press Enter");
input.setAttribute("maxLength", "4");
input.setAttribute("size", "50");
var parent = document.getElementById("inputDiv");
parent.appendChild(input);
parent.style.borderStyle = 'solid';
parent.style.borderWidth = '0px 0px .5px 0px';
parent.style.margin = 'auto';


function changeText() {
  var timer = 0;
  var fadeOutTimer = 1000;
  for (let i = 0; i < dialogue.length; i++) {
    setTimeout(fadeIn, timer);
    setTimeout(fadeOut, fadeOutTimer);
    setTimeout(function () {
      document.getElementById('h1').innerHTML = dialogue[i];
    }, timer);
    timer = (timer + 3000) * 1;
    fadeOutTimer = (fadeOutTimer + 3000) * 1.1;
    console.log(timer, fadeOutTimer);
  }
}

function welcome(callback) {
  changeText();
  callback();
}
welcome(function () {
  function userNameInput() {
    function pressEnter() {
      var userName = input.value;
      if (event.keyCode == 13) {
        document.getElementById('h1').innerHTML = "Nice to meet you" +
          " " + userName + "!";
      }
    }
    input.addEventListener("keyup", pressEnter);
  }
  userNameInput();
});
like image 654
Nhan Bui Avatar asked Jun 16 '19 00:06

Nhan Bui


1 Answers

If I wanted to summarize, the problem you're running in is the following one:

You have two functions that use setTimeout to execute some code with a delay. As setTimeout is not blocking, it will "instantly" register the callback of setTimeout and continue executing the rest of the function.

function a() {
    setTimeout(function() {
        console.log('a');
    }, 500)
} 

function b() {
    setTimeout(function() {
        console.log('b');
    }, 250)
}

a();
b();

Here you would like to have "a" after 500ms then "b" after another 250ms but you get "b" after 250ms and "a" after another 250ms.

The old way of doing this would be to use a callback like this:

function a(callback) {
    setTimeout(function() {
        console.log('a');
        callback();
    }, 500)
} 

function b() {
    setTimeout(function() {
        console.log('b');
    }, 250)
}

a(b)

Thus, a will call b itself.

A modern way of doing this would be to use promises/async/await:

function a() {
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log('a');
            resolve();
        }, 500)
    });
}

function b() {
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log('b');
            resolve();
        }, 250);
    });
}

and then call:

a().then(b).then(function() {/* do something else */})

or, within an async function:

async function main() {
    await a();
    await b();
    // do something else
}

main()
like image 139
S. Pellegrino Avatar answered Sep 23 '22 11:09

S. Pellegrino