Async Await Promise does not work : promiseresult is undefined - javascript

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);
}```

Related

Async JS validation issues for html textarea

I'm trying to replicate the code in this article:
https://depth-first.com/articles/2020/08/24/smiles-validation-in-the-browser/
What I'm trying to do different is that I'm using a textarea instead of input to take multi-line input. In addition to displaying an error message, I also want to display the entry which doesn't pass the validation.
The original validation script is this:
const path = '/target/wasm32-unknown-unknown/release/smival.wasm';
const read_smiles = instance => {
return smiles => {
const encoder = new TextEncoder();
const encoded = encoder.encode(`${smiles}\0`);
const length = encoded.length;
const pString = instance.exports.alloc(length);
const view = new Uint8Array(
instance.exports.memory.buffer, pString, length
);
view.set(encoded);
return instance.exports.read_smiles(pString);
};
};
const watch = instance => {
const read = read_smiles(instance);
document.querySelector('input').addEventListener('input', e => {
const { target } = e;
if (read(target.value) === 0) {
target.classList.remove('invalid');
} else {
target.classList.add('invalid');
}
});
}
(async () => {
const response = await fetch(path);
const bytes = await response.arrayBuffer();
const wasm = await WebAssembly.instantiate(bytes, { });
watch(wasm.instance);
})();
For working with a textarea, I've changed the watch function to this and added a <p id="indicator"> element to the html to display an error:
const watch = instance => {
const read = read_smiles(instance);
document.querySelector("textarea").addEventListener('input', e => {
const { target } = e;
var lines_array = target.value.split('/n');
var p = document.getElementById("indicator");
p.style.display = "block";
p.innerHTML = "The size of the input is : " + lines_array.length;
if (read(target.value) === 0) {
target.classList.remove('invalid');
} else {
target.classList.add('invalid');
}
});
}
I'm not even able to get a count of entries that fail the validation. I believe this is async js and I'm just a beginner in JavaScript so it's hard to follow what is happening here, especially the part where the function e is referencing itself.
document.querySelector("textarea").addEventListener('input', e => {
const { target } = e;
Can someone please help me in understanding this complicated code and figuring out how to get a count of entries that fail the validation and also printing the string/index of the same for helping the user?
There is a mistake in you code to count entries in the textarea:
var lines_array = target.value.split('\n'); // replace /n with \n
You are asking about the function e is referencing itself:
The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables. You can find more informations Mdn web docs - Destructuring object

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.

How do you call a asynchronous function using setInterval?

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>

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