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

1

8

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();
});
Underwing answered 16/6, 2019 at 0:4 Comment(0)
H
11

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()
Hypotonic answered 16/6, 2019 at 0:28 Comment(2)
is it better to use promises/async/wait instead of callbacks? i prefer using callbacks because of the syntax but does it have drawbacks?Tuba
Performance-wise, they should be equivalent. Promises where invented to prevent what is called "callback hell", the encapsulation of callbacks into each other (and its indentation problem). Promises allow you to keep every thing at the same level with the .then syntax. Still, they are pretty verbose and the await/async syntax allows you to use them under the hood without their ugliness, with a syntax looking synchronous await a(); await b(); etc. The async keyword allows you to implicitly resolve a promise without declaring it. It covers a lot of cases and is the modern way of doing.Hypotonic

© 2022 - 2024 — McMap. All rights reserved.