How to wait for a promise inside an each loop? - javascript

New to promises - trying to work out the logic.
I'm trying to grab youtube preview thumbnails automatically like this answer advises:
const youtubeAnimation = id => {
return fetch(
`https://cors-anywhere.herokuapp.com/https://www.youtube.com/results?search_query=${id}&sp=QgIIAQ%253D%253D`,
{ headers: {} }
)
.then(r => r.text())
.then(html => {
const found = html.match(
new RegExp(`["|']https://i.ytimg.com/an_webp/${id}/(.*?)["|']`)
);
if (found) {
try {
const url = JSON.parse(found[0]);
return url;
} catch (e) {}
}
throw "not found";
})
.catch(e => {
return false;
});
};
I store the youtube ID in the video attribute's container, and then do this which works well for a single video on the page:
youtubeAnimation($('.video').attr('youtube')).then(function (value) {
$('<img class="videopreview" src="' + value + '">').insertBefore($('.video'));
});
But I need to be able to run this with a variable number of $('.video') elements on the page. I've tried putting it in an each loop:
$(".video").each(function () {
youtubeAnimation($(this).attr('youtube')).then(function (value) {
$('<img class="videopreview" src="' + value + '">').insertBefore($(this));
});
});
But all this does is add the preview thumbnail to the last $('.video') element. When testing with console.log I can see that all preview images are being discovered, but that only the last image is applied to the last element. I need to somehow pause the each loop until youtubeAnimation does its thing, before proceeding to the next element.
What are the additional steps required to make this all work? And also, what is the reason that answer gave youtubeAnimation as a const, and not a function?

One option that I would try is:
$(".video").each(async () => {
const animation = await youtubeAnimation($(this).attr('youtube'));
$('<img class="videopreview" src="' + value + '">').insertBefore($(this));
});
It utilizes arrow function to preserve this and async/await to make the code more readable.

Related

How add two values[1,2 elements] and verify its equal to result[3 element]

I need to verify if the sum of two elements is equal to the result shown on the UI.
Below is the html structure for the three elements and need to do the calculation on the h1 tags.
Below is the code I am trying to execute but nothing comes up in the test. The test runs and passes but nothing in the body.
it.only(' Verify Sum Of Active and Paused Jobs is Equal To Total Jobs Count ', () => {
function activejobnumber() {
return cy.get('[data-cy="jobSummaryActiveJobsStatTitle"]>h1')
.invoke('text').then(parseInt).as('actnum');
}
function pausedjobnumber() {
return cy.get('[data-cy="jobSummaryPausedJobsStatTitle"] > h1')
.invoke('text').then(parseInt).as('pasnum');
}
function add() {
const totaljobs = activejobnumber() + pausedjobnumber();
cy.log(totaljobs);
}
});
Also, if someone could point out why it isn't working, would really appreciate it.
Using functions to encapsulate each number is fine, but remember that commands inside are still asynchronous, and they are not returning numbers, they are returning Chainers.
If you did it without functions, this is what it would look like
cy.get('[data-cy="jobSummaryActiveJobsStatTitle"]>h1')
.invoke('text').then(parseInt).as('actnum')
.then(actnum => {
cy.get('[data-cy="jobSummaryPausedJobsStatTitle"] > h1')
.invoke('text').then(parseInt).as('pasnum')
then(pasnum => {
const totaljobs = actnum + pasnum
cy.log(totaljobs)
})
})
so with functions you need to repeat that pattern
function add() {
activejobnumber().then(actnum => {
pausedjobnumber().then(pasnum => {
const totaljobs = actnum + pasnum;
cy.log(totaljobs);
})
})
}
or take advantage of the alias for each number
function add() {
activejobnumber()
pausedjobnumber()
// wait for above async calls to finish
cy.then(function() {
const totaljobs = this.actnum + this.pasnum;
cy.log(totaljobs);
})
}
You can directly invoke text, convert them to numbers and add it like this:
cy.get('[data-cy="jobSummaryActiveJobsStatTitle"]>h1')
.invoke('text')
.then((actnum) => {
cy.get('[data-cy="jobSummaryPausedJobsStatTitle"] > h1')
.invoke('text')
.then((pasnum) => {
//Adding + converts string to number
const totaljobs = +actnum + +pasnum
cy.log(totaljobs)
})
})

Keep page loading until map is finished

I have the following code, where I fetch an API and populate a container inside a function
let forms = [];
fetch('http://localhost:3000/getmenu')
.then((res) => res.json())
.then((data) => {
forms = favorites = data;
showForms(forms);
})
.catch(console.log('catch'));
function showForms(arrayForms) {
container.innerHTML = '';
arrayForms.map(
({ NUMERO, DESCRICAO, GRUPO, AUDIT_DATA }) => {
container.innerHTML = `foo`
}
However, the page loads extremely fast but the results take a few seconds to show up.
How can I keep the page in the loading state until the funciton is complete?
You can use ternary operator
arrayForms ? arrayForms.map(
({ NUMERO, DESCRICAO, GRUPO, AUDIT_DATA }) => {
container.innerHTML = `foo`
}) : "Loading data..."
SO I simply used a ternary operator after the map like so
container.innerHTML == '' ? console.log('not yet') : console.log('now');
which reads when the map has finished. This bypasses a problem I was having where a line after map() is also be executed before map().

How do I get Data from Algolia searchClient to function in JavaScript?

I have a problem to get data from variables inside an inner Javascript function to the outer function.
What I am trying to do is to search Algolia-hits from a special category to show the first hit that has an image. So I can use this image for an category-overview. All works fine, except that i dont manage to get the img-Url from the hits.
My function looks like:
methods: {
getCategoryPicture(categoryName) {
const index = this.searchClient.initIndex("Testindex");
var FacetFilterString = "Kategorie: " + categoryName;
var imgUrl = "";
// search for hits of category
index
.search("", {
facetFilters: [FacetFilterString],
attributesToRetrieve: ["img", "Marke"],
hitsPerPage: 50
})
.then(({ hits }) => {
// search for first hit with img
var img = ""
hits.forEach((element) => {
if (element["img"] != undefined)
{
img = "www.someurl.com/" + element["img"];
console.log(img);
return img
}
});
this.imgUrl = img
return img;
});
return imgUrl;
},
},
The function finds the url, I can log it, but I dont manage to get it from the inner ".then"-function to the outer function. I cant reach the variables of the outer-function. "this" doesnt work eather. (because it points to the module, not to the outer function, i guess?).
I just want to return the first img-url the function finds.
Edit: I updated the Code, so it might be more clear. The function "setImgUrl" logs the picture perfectly, but it does not update the outer variable "imgUrl".
methods: {
getCategoryPicture(categoryName) {
const index = this.searchClient.initIndex("Testindex");
var FacetFilterString = "Kategorie: " + categoryName;
var imgUrl = "";
index
.search("", {
facetFilters: [FacetFilterString],
attributesToRetrieve: ["img", "Marke"],
hitsPerPage: 50
})
.then(({ hits }) => {
var img = ""
hits.forEach((element) => {
if (element["img"] != undefined)
{
img = "www.someurl.com/" + element["img"];
setImgUrl(img);
}
});
return img
});
function setImgUrl(img) {
imgUrl = img;
console.log(img);
}
return imgUrl;
},
},

programmatically use w3c css/html validators with custom html

edit
I've got it working for CSS, only HTML validation to go.
let css = Array.from(document.styleSheets)
.reduce((combinedsheet,sheet) => sheet.rules?
combinedsheet + Array.from(sheet.rules)
.reduce((p,c) => p+c.cssText+'\n', ''):
combinedsheet, '')
try {
document.querySelector('a.validation.css').href =
'https://jigsaw.w3.org/css-validator/validator?text=' +
encodeURIComponent(css) +
'&profile=css3&usermedium=all&warning=1&vextwarning='
document.querySelector('a.validation.html').href =
'https://validator.w3.org/nu/?doc=' +
encodeURIComponent(document.querySelector('html'))
} catch (e) {
// this will fail before page fully loads, and we can be silent about that
}
edit #2
I've got it working. The only problem is that this uses a "popup" instead of opening a window silently like target="blank" would. I'm using an onlick method:
get_html_validation: function () {
let fd = new FormData()
fd.append('fragment', '<!DOCTYPE html>' +
document.querySelector('html').outerHTML)
fd.append('prefill', 0)
fd.append('doctype', 'Inline')
fd.append('prefill_doctype', 'html401')
fd.append('group', 0)
axios.post("https://validator.w3.org/nu/#textarea", fd)
.then(response => {
let win=window.open('about:blank')
console.log(win)
with(win.document)
{
open()
write(response.data)
close()
}
})
.catch(e => console.error(e))
}
original
I'd like to use these validators programmatically:
https://validator.w3.org/nu
http://jigsaw.w3.org/css-validator/validator
— they work just fine if you pass a URL as a parameter, so long as the document doesn't have javascript that manipulates the dom. But... mine does.
How can I hand these sites custom html to check using javascript in the browser? I'm looking to do something like:
onValidateDOM = () => {
let toValidate = '<!DOCTYPE html>' + document.querySelector('html').outerHTML
let returnURLs = []
returnURLs.push(w3cCSS.validate(toValidate), w3cHTML.validate(toValidate)
return returnURLs
}
Set toValidate passed to encodeURIComponent() as value to doc= query
fetch(`https://validator.w3.org/nu/?doc=${encodeURIComponent(toValidate)}`)
.then(response => response.text())
.then(text => console.log(text));

Writing/adjusting object from within callback

I'm working on a meteor react project and want to use load data from local storage which happens async. Unfortunately I wasn't able to get the data out of callback even with binds. I tried multiple ways but could not get any of them to work, surely I'm missing something.
I stripped as much away as I could but had to keep some for the context.
From my understanding it can only be related to the track object as setting those simple Integers, Booleans works fine.
render() {
const { audioCollection } = this.props; // database collection, async hence the following if check
if (audioCollection) {
this.tracks = audioCollection.audio(); // setting tracks for later use
// make sure tracks are loaded and only run once, as we do this in the react renderer
if (this.tracks && !this.state.tracksLoaded) {
var trackLoadedCount = 0;
this.tracks.forEach((track, i) => { // I tried to use forEach and map here
// now we try to load the data from local storage and if it fails fall back to the remote server
LocalForage.getItem(track.file_id).then(function(err, file) {
if (!err && file) {
console.log(track.file_id + ' from cache')
var blob = new Blob([file]);
fileURI = window.URL.createObjectURL(blob);
} else {
console.log(track.file_id + ' from database')
fileURI = audioCollection.audioLink(track.file_id);
}
track.fileURI = fileURI; // assigning the retrieved data uri to the track object, also tried to set it on the original parent object not the extracted one from the forEach/map
console.log(fileURI + ' ' + track.fileURI) // yes, both are set
trackLoadedCount++; // increasing the tracks loaded count, to calculate if all have been loaded and to test if it can write outside of the callback, which it does
// if all files have been processed, set state loaded, this works too.
if (trackLoadedCount == this.tracks.length) {
this.setState({
tracksLoaded: true,
})
}
}.bind(track, this))
});
}
}
// once all has been processed we try to retrieve the fileURI, but it isn't set :(
if (audioCollection && this.tracks && this.state.tracksLoaded) {
console.log('all loaded ' + this.tracks.length)
this.tracks.map(track => {
console.log('track: ' + track.file_id + ' ' + track.fileURI) // file_id is set, fileURI is not
})
}
// we only log
return (
<Content {...this.props}>
<div>just console output</div>
</Content>
);
}
I tried more approaches:
Writing the tracks as an array to state like tracksLoaded (didn't work work)
Defining a new var before the async call and setting its values from within the callback, like trackLoadedCount (with and without bind) (doesn't work)
Why isn't this working while its working for tracksLoaded and trackLoadedCount?
Update regarding Firice Nguyen Answer
render() {
const { audioCollection } = this.props;
if (audioCollection) {
this.tracks = audioCollection.audio();
if (this.tracks && !this.state.tracksLoaded) {
var trackLoadedCount = 0;
this.tracks.forEach((track, i, trackArray) => {
LocalForage.getItem(track.file_id).then(function(err, file) {
if (!err && file) {
console.log(track.file_id + ' from cache')
var blob = new Blob([file]);
fileURI = window.URL.createObjectURL(blob);
} else {
console.log(track.file_id + ' from database')
fileURI = audioCollection.audioLink(track.file_id);
}
track.fileURI = fileURI;
console.log('1. ' + track.file_id + ' ' + track.fileURI);
trackArray[i] = track;
console.log('2. ' + track.file_id + ' ' + trackArray[i].fileURI);
trackLoadedCount++;
if (trackLoadedCount == this.tracks.length) {
this.setState({
tracksLoaded: true,
})
}
}.bind(track, this))
});
}
}
if (audioCollection && this.tracks && this.state.tracksLoaded) {
console.log('all loaded ' + this.tracks.length)
this.tracks.map(track => {
console.log('3. ' + track.file_id + ' ' + track.fileURI) // file_id is set, fileURI is not
})
}
return (
<Content {...this.props}>
<div>just console output</div>
</Content>
);
}
returns
MXqniBNnq4zCfZz5Q from database
1. http://localhost:3000/cdn/storage/files/MXqniBNnq4zCfZz5Q/original/MXqniBNnq4zCfZz5Q.m4a
2. http://localhost:3000/cdn/storage/files/MXqniBNnq4zCfZz5Q/original/MXqniBNnq4zCfZz5Q.m4a
keBWP6xb9PyEJhEzo from database
1. http://localhost:3000/cdn/storage/files/keBWP6xb9PyEJhEzo/original/keBWP6xb9PyEJhEzo.m4a
2. http://localhost:3000/cdn/storage/files/keBWP6xb9PyEJhEzo/original/keBWP6xb9PyEJhEzo.m4a
K2J2W9W26DDBNoCcg from database
1. http://localhost:3000/cdn/storage/files/K2J2W9W26DDBNoCcg/original/K2J2W9W26DDBNoCcg.m4a
2. http://localhost:3000/cdn/storage/files/K2J2W9W26DDBNoCcg/original/K2J2W9W26DDBNoCcg.m4a
all loaded 3
3. MXqniBNnq4zCfZz5Q undefined
3. keBWP6xb9PyEJhEzo undefined
3. K2J2W9W26DDBNoCcg undefined
hence the issue persists.
The forEach give out a copy of the element. The track is just a copy, not the original one. It does not point to the element in your array. You can try this:
this.tracks.forEach(track, i, trackArray) => {
// change `track` value
...
trackArray[i] = track;
});
Or try map method

Categories

Resources