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.)