How do you call a asynchronous function using setInterval? - javascript

I get a random word and then use the word to generate a GIF.
My code here runs for only one time. I want it to generate another word and get another image without refreshing the browser.
So,I have used setInerval();by passing the the function that gets the image using fetch()
const section = document.getElementById('main');
const text = document.querySelector('.word');
let wordurl = 'https://random-word-api.herokuapp.com/word?number=1&swear=0';
let giphyapikey = '*****************';
//Setinterval
setInterval(wordgif(), 5000);
//make WordGIF call
function wordgif() {
wordGIF().then(results => {
text.innerHTML = results.word;
section.innerHTML = `<img src=${results.imgurl}>`;
}).catch(err => console.error(err))
}
//Async/await
async function wordGIF() {
let fetchword = await fetch(wordurl);
let word = await fetchword.json();
console.log(word)
let fetchgif = await fetch(`http://api.giphy.com/v1/gifs/search?q=${word}&api_key=${giphyapikey}&limit=1`);
let gif = await fetchgif.json();
console.log(gif)
let imgurl = gif.data[0].images['fixed_height_small'].url;
return {
word: word,
imgurl: imgurl
}
}
As far as my understanding shouldn't
setInterval(wordgif(), 5000);
be called every 5 seconds and generate a new word and image?
How do you setInterval with asynchronus function?

setInterval(wordgif(), 5000);
This code will call wordgif, then pass the result of that function to setInterval. It is equivalent to:
const wordgifResult = wordgif();
setInterval(wordgifResult, 5000);
Since wordgif doesn't return a value, calling setInterval has no real effect.
If you want setInterval to call wordgif, then you need only pass a reference to the function as the argument:
setInterval(wordgif, 5000);

I've updated your code a little bit.
You should clear the interval regularly.
You don't need to return anything from the async function, just do what you want to do inside the function.
Must check if the gif file available before rendering it.
const section = document.getElementById('main');
const text = document.querySelector('.word');
let wordurl = 'https://random-word-api.herokuapp.com/word?number=1&swear=0';
let giphyapikey = '62urPH2PxR2otT2FjFFGNlvpXmnvRVfF';
wordGIF(); // can load first gif before interval
//Setinterval
let interval;
if (interval) clearInterval(interval);
interval = setInterval(wordGIF, 5000);
//Async/await
async function wordGIF() {
let fetchword = await fetch(wordurl);
let word = await fetchword.json();
let fetchgif = await fetch(`https://api.giphy.com/v1/gifs/search?q=${word}&api_key=${giphyapikey}&limit=1`);
let gif = await fetchgif.json();
console.log('Gif available: ' + (gif && Object.keys(gif.data).length > 0));
if (gif && Object.keys(gif.data).length > 0) {
let imgurl = gif.data[0].images['fixed_height_small'].url;
text.innerHTML = word;
section.innerHTML = `<img src=${imgurl}>`;
}
}
.as-console-wrapper {
max-height: 20px !important;
}
<div id="main"></div>
<div class="word"></div>

Related

How to make react stop duplicating elements on click

The problem is that every time I click on an element with a state things appear twice. For example if i click on a button and the result of clicking would be to output something in the console, it would output 2 times. However in this case, whenever I click a function is executed twice.
The code:
const getfiles = async () => {
let a = await documentSpecifics;
for(let i = 0; i < a.length; i++) {
var wrt = document.querySelectorAll("#writeto");
var fd = document.querySelector('.filtered-docs');
var newResultEl = document.createElement('div');
var writeToEl = document.createElement('p');
newResultEl.classList.add("result");
writeToEl.id = "writeto";
newResultEl.appendChild(writeToEl);
fd.appendChild(newResultEl);
listOfNodes.push(writeToEl);
listOfContainers.push(newResultEl);
wrt[i].textContent = a[i].data.documentName;
}
}
The code here is supposed to create a new div element with a paragraph tag and getting data from firebase firestore, will write to the p tag the data. Now if there are for example 9 documents in firestore and i click a button then 9 more divs will be replicated. Now in total there are 18 divs and only 9 containing actual data while the rest are just blank. It continues to create 9 more divs every click.
I'm also aware of React.Strictmode doing this for some debugging but I made sure to take it out and still got the same results.
Firebase code:
//put data in firebase
createFileToDb = () => {
var docName = document.getElementById("title-custom").value; //get values
var specifiedWidth = document.getElementById("doc-width").value;
var specifiedHeight = document.getElementById("doc-height").value;
var colorType = document.getElementById("select-color").value;
parseInt(specifiedWidth); //transform strings to integers
parseInt(specifiedHeight);
firebase.firestore().collection("documents")
.doc(firebase.auth().currentUser.uid)
.collection("userDocs")
.add({
documentName: docName,
width: Number(specifiedWidth), //firebase-firestore method for converting the type of value in the firestore databse
height: Number(specifiedHeight),
docColorType: colorType,
creation: firebase.firestore.FieldValue.serverTimestamp() // it is possible that this is necessary in order to use "orderBy" when getting data
}).then(() => {
console.log("file in database");
}).catch(() => {
console.log("failed");
})
}
//get data
GetData = () => {
return firebase.firestore()
.collection("documents")
.doc(firebase.auth().currentUser.uid)
.collection("userDocs")
.orderBy("creation", "asc")
.get()
.then((doc) => {
let custom = doc.docs.map((document) => {
var data = document.data();
var id = document.id;
return { id, data }
})
return custom;
}).catch((err) => {console.error(err)});
}
waitForData = async () => {
let result = await this.GetData();
return result;
}
//in render
let documentSpecifics = this.waitForData().then((response) => response)
.then((u) => {
if(u.length > 0) {
for(let i = 0; i < u.length; i++) {
try {
//
} catch(error) {
console.log(error);
}
}
}
return u;
});
Edit: firebase auth is functioning fine so i dont think it has anything to do with the problem
Edit: This is all in a class component
Edit: Clicking a button calls the function createFileToDb
I think that i found the answer to my problem.
Basically, since this is a class component I took things out of the render and put some console.log statements to see what was happening. what i noticed is that it logs twice in render but not outside of it. So i took the functions out.
Here is the code that seems to fix my issue:
contain = () => {
const documentSpecifics = this.waitForData().then((response) => {
var wrt = document.getElementsByClassName('writeto');
for(let i = 0; i < response.length; i++) {
this.setNewFile();
wrt[i].textContent = response[i].data.documentName;
}
return response;
})
this.setState({
docs: documentSpecifics,
docDisplayType: !this.state.docDisplayType
})
}
As for creating elements i put them in a function so i coud reuse it:
setNewFile = () => {
const wrt = document.querySelector(".writeto");
const fd = document.querySelector("#filtered-docs");
var newResultEl = document.createElement('div');
newResultEl.classList.add("result");
var wrtEl = document.createElement('p');
wrtEl.classList.add("writeto");
fd.appendChild(newResultEl);
newResultEl.appendChild(wrtEl);
}
The firebase and firestore code remains the same.
the functions are called through elements in the return using onClick.

I'm just starting with JS and I don't really understand how setTimeout works

I am developing a trivia page with a series of questions that I get through an API. When I give to start the game on the main page, I want to redirect me to a second page where the available categories will appear and when I click on them, the questions will appear with multiple answers.
Luckily I have managed to do all this, however, when I switch from one page to another I have not been able to get the categories to appear. They only appear if I am already on that page and I create a button to call the function that displays them.
I had thought of doing this function in this way so that it would switch me to the other page and then load the questions but I have not been able to get it to work.
Does anyone have a solution or know why it is not working?
function get_dataJson() {
let data_json = [];
fetch('https://opentdb.com/api.php?amount=10')
.then(response => response.json())
.then(data => data.results.forEach(element => {
data_json.push(element);
}));
return data_json;
}
let trivial_data = get_dataJson();
function startGame() {
let div_username = document.createElement("div");
let name = document.createElement("h2");
name.innerHTML = document.getElementById("name").value;
div_username.appendChild(name);
let categories = get_categories(trivial_data);
setTimeout("location.href='./game.html'", 1000);
setTimeout(function(){
document.getElementById('nav-game').appendChild(div_username);
show_categories(categories);
},2000);
}
function get_categories(trivial_data) {
let categories = [];
trivial_data.forEach(element => {
categories.push(element.category)
});
console.log(categories);
return categories;
}
function show_categories (categories) {
let div_categories = document.createElement("div");
let count = 0;
categories.forEach ( element => {
let element_category = document.createElement("button");
element_category.innerHTML = element;
element_category.id = count;
element_category.onclick = function() {
get_question(element_category.id);
};
count++;
div_categories.appendChild(element_category);
});
div_categories.className = 'categories';
document.getElementById("categories").appendChild(div_categories);
}
function get_question (pos) {
document.getElementById("categories").style.displays = 'none';
let question = document.createElement("h2");
question.innerHTML = trivial_data[pos].question;
let correct_answer = trivial_data[pos].correct_answer;
let incorrect_answers = trivial_data[pos].incorrect_answers;
let options = incorrect_answers.concat(correct_answer);
options = options.sort();
let div_choices = document.createElement("div");
options.forEach(element => {
let choice = document.createElement('button');
choice.innerHTML = element;
choice.id = 'true' ? element == correct_answer : 'false';
div_choices.appendChild(choice);
});
div_choices.className = 'answers';
document.getElementById("questions").appendChild(question);
document.getElementById("questions").appendChild(div_choices);
}
The first thing you should do is make sure you understand how to use fetch to get data asynchronously
async function get_dataJson() {
const response = await fetch('https://opentdb.com/api.php?amount=10');
const json = await response.json();
return json.results;
}
(async function(){
const trivial_data = await get_dataJson();
console.log(trivial_data);
})()
Then, rather than changing page, just stay on the same page and add/remove/hide elements as necessary - it looks like you have that bit sorted.

Async Await Promise does not work : promiseresult is undefined

I have not being able to use the getvoices() function from the speechsynthesis framework.
My basic idea is to be able to click on the document and be able to read the text of the element in Spanish. Therefore, I am trying to use the promise and await paradigm to set the voice variable upon loading the document based on the list of available voices.
What am I missing? thanks!
var synth = window.speechSynthesis;
function getVoices() {
return new Promise(resolve => {
let voices = speechSynthesis.getVoices()
resolve(voices)
})
}
async function getVoz() {
const Voces = await getVoices();
for(voz of Voces){
if (voz.lang=="es-ES"){
return voz
}
}
}
var voice = getVoz()
function sayit(){
var div = document.getElementById('main')
var what=div.innerHTML
var hi = new SpeechSynthesisUtterance(what);
hi.pitch = 1;
hi.rate = 1;
hi = voice;
synth.speak(hi);
}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css">
<script src="script.js"></script>
</head>
<body>
<div id="main" onclick="sayit()"> Hola Mundo </div>
</body>
</html>
I see at least 3 mistakes:
speechSynthesis.getVoices() is a synchronous method, so wrapping in in a Promise does nothing. It's not harmful, just pointless.
var voice = getVoz() - after this, voice is now the promise, not the result. However you cannot add await here since you are not inside an async function. You need to use .then() and a callback.
hi = voice; - this overwrites the entire hi variable with the Promise. Probably not what was intended.
Here's how I would write it:
function sayit(){
var hi = new SpeechSynthesisUtterance(document.getElementById('main').innerHTML);
hi.pitch = 1;
hi.rate = 1;
hi.voice = window.speechSynthesis.getVoices().find(voz => voz.lang == "es-ES");
window.speechSynthesis.speak(hi);
}
After some more readings, I found a couple of things that solve my problem:
the voicelist is loaded async to the page. An onvoiceschanged event is needed.
this code works (await getVoices()) and the previous one does not const Voces = await getVoices()
The following code works
let synth = window.speechSynthesis;
let esp= 0
const getVoices = () => {
return new Promise(resolve => {
let voices = synth.getVoices()
if (voices.length) {
resolve(voices)
return
}
const voiceschanged = () => {
voices = synth.getVoices()
resolve(voices)
}
speechSynthesis.onvoiceschanged = voiceschanged
})
}
const getVoz = async () => {
(await getVoices()).forEach(voice => {
console.log(voice.name, voice.lang)
if(voice.lang=="es-ES"){
esp= voice
}
})
}
getVoz()
function sayit(){
let div = document.getElementById('main')
let what=div.innerHTML
let hi = new SpeechSynthesisUtterance(what);
hi.pitch = 1;
hi.rate = 1;
hi.voice = esp;
synth.speak(hi);
}```

Looping through multiple links properly

I am very new to puppeteer. I started yesterday and I'm trying to make a program that flips through a url that incrementally stores player id's one after the other and saves the player stats using neDB. There are thousands of links to flip through and I have found that if i use a for loop my computer basically crashes because 1,000 Chromiums try to open all at the same time. Is there a better way, or proper way to do this? Any advice would be appreciated.
const puppeteer = require('puppeteer');
const Datastore = require('nedb');
const database = new Datastore('database.db');
database.loadDatabase();
async function scrapeProduct(url){
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(url);
let attributes = [];
//Getting player's name
const [name] = await page.$x('//*[#id="ctl00_ctl00_ctl00_Main_Main_name"]');
const txt = await name.getProperty('innerText');
const playerName = await txt.jsonValue();
attributes.push(playerName);
//Getting all 12 individual stats of the player
for(let i = 1; i < 13; i++){
let vLink = '//*[#id="ctl00_ctl00_ctl00_Main_Main_SectionTabBox"]/div/div/div/div[1]/table/tbody/tr['+i+']/td[2]';
const [e1] = await page.$x(vLink);
const val = await e1.getProperty('innerText');
const skillVal = await val.jsonValue();
attributes.push(skillVal);
}
//creating a player object to store the data how i want (i know this is probably ugly code and could be done in a much better way)
let player = {
Name: attributes[0],
Athleticism: attributes[1],
Speed: attributes[2],
Durability: attributes[3],
Work_Ethic: attributes[4],
Stamina: attributes[5],
Strength: attributes[6],
Blocking: attributes[7],
Tackling: attributes[8],
Hands: attributes[9],
Game_Instinct: attributes[10],
Elusiveness: attributes[11],
Technique: attributes[12],
};
database.insert(player);
await browser.close();
}
//For loop to loop through 1000 player links... Url.com is swapped in here because the actual url is ridiculously long and not important.
for(let i = 0; i <= 1000; i++){
let link = 'https://url.com/?id='+i+'&section=Ratings';
scrapeProduct(link);
console.log("Player #" + i + " scrapped");
}
The easiest tweak would be to wait for each link to finish before starting the next:
(async () => {
for(let i = 0; i <= 1000; i++){
let link = 'https://url.com/?id='+i+'&section=Ratings';
await scrapeProduct(link);
console.log("Player #" + i + " scrapped");
}
})();
You could also allow only enough open as your computer can handle. This will require more resources, but will allow the process to finish faster. Figure out the limit you want, then do something like:
let i = 0;
const getNextLink = () => {
if (i > 1000) return;
let link = 'https://url.com/?id='+i+'&section=Ratings';
i++;
return scrapeProduct(link)
.then(getNextLink)
.catch(handleErrors);
};
Promise.all(Array.from(
{ length: 4 }, // allow 4 to run concurrently
getNextLink
))
.then(() => {
// all done
});
The above allows for 4 calls of scrapeProduct to be active at any one time - change the number as needed.
If you think that the issue with speed is reopening/closing the browser with each run, move browser to the global scope and initialize it to null. Then create a init function with something like:
async function init(){
if(!browser)
browser = await puppeteer.launch()
}
Allow pages to be passed to your scrapeProduct function. async function scrapeProduct(url) becomes async function scrapeProduct(url,page). Replace await browser.close() with await page.close(). Now your loop will look like this:
//For loop to loop through 1000 player links... Url.com is swapped in here because the actual url is ridiculously long and not important.
await init();
for(let i = 0; i <= 1000; i++){
let link = 'https://url.com/?id='+i+'&section=Ratings';
let page = await browser.newPage()
scrapeProduct(link,page);
console.log("Player #" + i + " scrapped");
}
await browser.close()
If you wanted to limit number of pages the browser will concurrently run you could create a function to do that:
async function getTotalPages(){
const allPages = await browser.pages()
return allPages.length
}
async function newPage(){
const MAX_PAGES = 5
await new Promise(resolve=>{
// check once a second to check on pages open
const interval = setInterval(async ()=>{
let totalPages = await getTotalPages()
if(totalPages< MAX_PAGES){
clearInterval(interval)
resolve()
}
},1000)
})
return await browser.newPage()
}
If you did this, in your loop you'd replace let page = await browser.newPage with let page = await newPage()

Stop a js function from loading on initial page load

I am trying to stop a JS function from loading every time a page is loaded, i wanted it to work only when a button is clicked. I have tried using onclick and .addEventListener.On using onclick, the output data displays before clicking the load data button and on using .addEventListener no data loads even on clicking the button. Any help would be appreciated, Thank you!
<html>
<head>
<title> Monitoring</title>
</head>
<body>
<div id="output"></div>
<script src="./lib/mam.web.min.js"></script>
<script>
const TRYTE_ALPHABET = 'SFRRJJVGGYJI';
const asciiToTrytes = (input) => {
let trytes = '';
for (let i = 0; i < input.length; i++) {
var dec = input[i].charCodeAt(0);
trytes += TRYTE_ALPHABET[dec % 27];
trytes += TRYTE_ALPHABET[(dec - dec % 27) / 27];
}
return trytes;
};
const trytesToAscii = (trytes) => {
let ascii = '';
for (let i = 0; i < trytes.length; i += 2) {
ascii += String.fromCharCode(TRYTE_ALPHABET.indexOf(trytes[i]) + TRYTE_ALPHABET.indexOf(trytes[i + 1]) * 27);
}
return ascii;
};
const outputHtml = document.querySelector("#output");
const mode = 'public'
const provider = ''
const mamExplorerLink = ``
// Initialise MAM State
let mamState = Mam.init(provider)
// Publish to tangle
const publish = async packet => {
// alert("coming into publish");
// Create MAM Payload - STRING OF TRYTES
const trytes = asciiToTrytes(JSON.stringify(packet))
const message = Mam.create(mamState, trytes)
// alert("p2");
// Save new mamState
mamState = message.state
// alert("p3");
// Attach the payload
await Mam.attach(message.payload, message.address, 3, 9)
// alert("p4");
outputHtml.innerHTML += `Published: ${packet}<br/>`;
// alert(message.root);
return message.root
}
const publishAll = async () => {
// alert("Yes 1.3");
const root = await publish('ALICE')
return root
}
// Callback used to pass data out of the fetch
const logData = data => outputHtml.innerHTML += `Fetched and parsed ${JSON.parse(trytesToAscii(data))}<br/>`;
(async function GKpublishAll(){
const root = await publishAll();
// alert(root);
outputHtml.innerHTML += `${root}`;
})();
</script>
<form>
Publish message :
<input type="submit" onclick="GKpublishAll()">
</form>
</body>
</html>
This piece of code defines and immediately calls the GKpublishAll function, if you don't want it called on page load, you should change the code from this:
(async function GKpublishAll(){
const root = await publishAll();
// alert(root);
outputHtml.innerHTML += `${root}`;
})();
to this:
async function GKpublishAll(){
const root = await publishAll();
// alert(root);
outputHtml.innerHTML += `${root}`;
}
Then, to use it when a button is clicked, you attach it to a button's onclick handlers like so:
<button onclick="GKpublishAll()">Click me</button>
There are some unclear/uncertain/hidden parts of the code you posted. maybe you should start with a simplified version of it that shows the definitions and invocations.
for example, there is a function called logData you defined but never invoked/used. where should it be used, I didn't see any fetch here
#Ermir answer is the right answer for the question "why this piece of code works even without clicking the button?"
On the <button> tag, you may want to add a attribute instead of adding a javascript event: mouseclick="function()". If it gives an error, try click="function()". Or try onclick="function()".
Hope it helps!

Categories

Resources