Home > Mobile >  Javascript code only works when debugging
Javascript code only works when debugging

Time:07-21

var selectedLetter = "global right now";

function GetLink() {
  fetch("https://random-word-api.herokuapp.com/word")
    .then(
      r => {
        if (r.status != 200)
          return;
        r.json().then(
          c => {
            for (let i = 0; i < c[0].length; i  ) {
              p = document.createElement("div");
              p.innerHTML = "_";
              if (i == 0) {
                p.setAttribute("id", "first");
              }
              p.classList.add("word");
              document.querySelector("#word").appendChild(p)
            }

          });
      }
    )
}

function SetupKeyboard() {
  letters = document.getElementsByClassName("word");
  selectedLetter = document.getElementById("first");
}

window.addEventListener('load', () => {
  GetLink();
  SetupKeyboard();
});
html,
body,
#wrapper {
  height: 100%;
  width: 100%;
}

#title {
  font-size: xx-large;
  text-align: center;
}

#word {
  font-size: xx-large;
  text-align: center;
}

.word {
  display: inline;
  margin: 2px;
  background-color: beige;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hangman</title>
  <link rel="stylesheet" href="style.css">
  <script src="index.js"></script>
</head>

<body>
  <div id="title">
    Hangman
  </div>
  <div id="word">

  </div>
</body>

</html>

It seems to be a simple problem but it's driving me insane.

function SetupKeyboard()
{
  letters=document.getElementsByClassName("word");    
  selectedLetter=document.getElementById("first");
}

I need the selected letter to be the first element of the "letters" array but for some reason selectedLetter=letters[0] gave back "undefined". I tried this approach too and it's the same problem. I am obviously missing something.

When I debug the code it works properly and the console logs everything as expected. When NOT in debugger the value of selectedLetter is "undefined". Also, the problem is gone when I call the function manually through browser console as well.

I know the problem is not in the DOM not existing because I run it all like this:

window.addEventListener('load', ()=>{
    GetLink();
    SetupKeyboard();
});

CodePudding user response:

GetLink() is performing an asynchronous operation. Which means when SetupKeyboard() executes, that asynchronous operation has not yet completed and the target elements you're looking for don't exist.

Since fetch returns a Promise, you can return that from your function:

function GetLink() {
  return fetch("https://random-word-api.herokuapp.com/word")
  // the rest of the function...
}

Then, exactly as you do in your GetLink function, you can append .then() to that Promise to follow it up:

window.addEventListener('load', ()=>{
  GetLink().then(SetupKeyboard);
});

Note the lack of parentheses on SetupKeyboard. We're passing the function itself to .then(). This is structurally similar to using an explicit anonymous function to wrap the SetupKeyboard() call, like you do in your .then() callbacks already:

window.addEventListener('load', ()=>{
  GetLink().then(() => SetupKeyboard());
});

Edit: You appear to also be stacking/chaining your asynchronous operations incorrectly within GetLink. Which means that my suggestion above is awaiting the first asynchronous operation, but not the second. Make GetLink an async function to make the syntax cleaner, and await the asynchronous operations therein:

async function GetLink()
{
  let r = await fetch("https://random-word-api.herokuapp.com/word");
  if (r.status != 200)
    return;
  let c = await r.json();
  for (let i = 0; i < c[0].length; i  ) {
    p = document.createElement("div");
    p.innerHTML = "_";
    if (i == 0) {
      p.setAttribute("id", "first");
    }
    p.classList.add("word");
    document.querySelector("#word").appendChild(p)
  }
}

This not only makes the syntax much cleaner and easier to follow, but effectively it chains all of the await operations into one resulting Promise chain, which you can then follow up:

window.addEventListener('load', ()=>{
  GetLink().then(SetupKeyboard);
});

CodePudding user response:

The answer was hidden in your comments:

The GetLink() function just grabs a random word from an API and makes the div elements with the class name "word".

GetLink() makes an API call, so is therefore asynchronous; so you're calling SetupKeyboard before GetLink has inserted the DOM elements SetupKeyboard is looking for.

You should instead ensure GetLink returns a promise, and do something like this in your window.load handler:

GetLink().then(SetupKeyboard)

...or else use async / await -- same thing, different syntax.

My understanding is that even though it's an API call that takes some time, it's wrapped in a sync function ("normal") therefore everything does wait for it's execution?

You've got it backwards; code will wait for async functions, if you explicitly tell it to do so using await or wrapping it in a Promise's then().

(Personally I think the Promises syntax is less confusing, especially for people new to the concept; I would suggest sticking with that until you're more comfortable working with asynchronous code.)

  • Related