Know how many promise are pending - javascript

My problem is the following :
I have an array of ids that I need to map to some database Ids using an HTTP request to my API like HTTP GET /foo/{id}
I need to wait for all values to show the data in my application
I'm currently doing this the following
async getValuesByIds({}, ids){
const valuesPromise = ids.map(async (id) => this.$axios.$get(`/foo/${id}`))
return Promise.all(valuesPromise)
}
And in the code before printing the values :
this.loading = true
this.getLeadsValuesByIds(idsArray).then(data => {
this.loading = false
this.values = data
})
The code is working fine but takes some times to run if i have a lot of ids.
In general, the first request ended in about 0.5 seconds and depending on the number of request, the last one can go up to 4 to 5 seconds
My goal here is to display a loading text informating the user how many request are left and how many are done.
Here is a short example using the jsonPlaceHolder API.
Basically what i want to have is instead of loading.. The number of request left (like {n} / 99 loaded
const loadData = () => {
const ids = Array.from({length: 99}, (_, i) => i + 1)
updateDataText('loading....')
const dataPromise = ids.map(async(id) => {
const post = await axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`)
return post.data
})
Promise.all(dataPromise).then(res => {
updateDataText(JSON.stringify(res))
})
}
const updateDataText = (text) => {
const div = document.getElementById('dataText')
div.innerText = text
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.27.2/axios.min.js"></script>
<button onclick="loadData()">LoadData</button>
<p id="dataText"></p>
Note : I'm using Nuxt, i don't know if that change something.

You could use a <progress> tag like this
I've also added inflight and finished counter if you prefer that
const loadData = () => {
const ids = Array.from({length: 99}, (_, i) => i + 1)
let nStarted = 0, nFinished=0;
const inflight = document.getElementById('inflight');
const finished = document.getElementById('finished');
const progress = document.getElementById('progress');
progress.max = ids.length;
updateDataText('loading....')
const dataPromise = ids.map(async(id) => {
nStarted++;
inflight.textContent = nStarted - nFinished;
const post = await axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`)
progress.value++;
nFinished++;
finished.textContent = nFinished;
inflight.textContent = nStarted - nFinished;
return post.data
})
Promise.all(dataPromise).then(res => {
updateDataText(JSON.stringify(res, null, 4))
})
}
const updateDataText = (text) => {
const div = document.getElementById('dataText')
div.innerText = text
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.27.2/axios.min.js"></script>
<button onclick="loadData()">LoadData</button>
<progress id="progress" value=0></progress>
<div>In Flight: <span id="inflight"></span></span>
<div>Finished: <span id="finished"></span></span>
<pre id="dataText"></pre>

Related

Access image property from array of objects and display randomly when button is clicked

I'm trying to access the image property from the array of objects from NASA API , so it can be displayed randomly when I click on a button
I want a different image to be shown whenever I click a button. The images are from an array of objects from NASA api.
document.querySelector('button').addEventListener('click', getFetch)
function getFetch() {
const url = 'https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=1000&api_key=1wCuyjoxfSrvsEbUpfWJvuBEMB5I0x79kXS0pfrg'
fetch(url)
.then(res => res.json()) // parse response as JSON
.then(data => {
console.log(data)
data.photos.forEach(element => {
document.querySelector('img').src = element.img_src
f
});
})
.catch(err => {
console.log(`error ${err}`)
});
}
I'd say that you are doing it the wrong way around - don't fetch up to 1000 images on click, fetch them first and cycle through them on click.
const button = document.querySelector('button');
const img = document.querySelector('img');
const imgUrl = 'https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=1000&api_key=1wCuyjoxfSrvsEbUpfWJvuBEMB5I0x79kXS0pfrg'
const makeImgCycler = (images) => {
return () => {
const n = Math.floor(Math.random() * images.length);
return images[n];
};
};
const fetchImgs = (url) => {
return fetch(url).then(r => r.json());
};
fetchImgs(imgUrl).then(data => {
const nextImage = makeImgCycler(data.photos);
button.disabled = null;
button.addEventListener('click', () => {
img.src = nextImage().img_src;
});
});
<button disabled>Next</button>
<img alt="">
Hint
The "Next" button is initially disabled until the images are loaded.
EDIT
Made makeImgCycler/nextImage return a random image. Not checking if it returns the same random image on subsequent clicks, though.
This is not fast so we fetch first and then allow showing
I have now implemented that we show unique random images until we have shown all, then we again show unique random images from a copy of the original array.
const randomImage = document.getElementById("randomImage");
const btn = document.querySelector('button');
const url = 'https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=1000&api_key=1wCuyjoxfSrvsEbUpfWJvuBEMB5I0x79kXS0pfrg'
let shown = [];
let images = [];
const getUnique = () => {
if (images.length === 0) images = shown.splice(0, shown.length); // copy back
const rnd = Math.floor(Math.random() * images.length);
const image = images.splice(rnd, 1)[0];
shown.push(image)
return image;
};
fetch(url)
.then(res => res.json()) // parse response as JSON
.then(data => {
images = data.photos.map(element => element.img_src) // .slice(0, 3); // for testing
btn.textContent = "Show image";
btn.addEventListener('click', function() {
randomImage.src = getUnique();
})
})
.catch(err => {
console.log(`error ${err}`)
});
<button type="button">Please wait...</button>
<img id="randomImage" src="" />
Previous simpler version
const randomImage = document.getElementById("randomImage");
const btn = document.querySelector('button');
const url = 'https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=1000&api_key=1wCuyjoxfSrvsEbUpfWJvuBEMB5I0x79kXS0pfrg'
fetch(url)
.then(res => res.json()) // parse response as JSON
.then(data => {
let images = data.photos.map(element => element.img_src);
btn.textContent = "Show image";
btn.addEventListener('click', function() {
const rnd = Math.floor(Math.random() * images.length);
randomImage.src = images[rnd];
})
})
.catch(err => {
console.log(`error ${err}`)
});
<button type="button">Please wait...</button>
<img id="randomImage" src="" />
First of all, please remove your API keys when posting to public forums.
The reason is that you are applying the image url to the same image object and so the last image to be iterated will be the one displayed. My recommendation would be to cache the images in an array somewhere and then choose a new random image when you click the button again.
The API should not have to be called more than once since it's making a HTTP request and will feel slow and sluggish depending on the distance from the user and the NASA servers
Your API is providing object array. If you want to show random image from an array you can use some like below:
let show = photos[Math.floor(Math.random() * photos.length)];

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.

How/When to remove child elements to clear search result?

Trying to clear my search result after I submit a new API call. Tried implementing gallery.remove(galleryItems); at different points but to no avail.
A bit disappointed I couldn't figure it out but happy I was able to get a few async functions going. Anyway, here's the code:
'use strict';
const form = document.querySelector('#searchForm');
const gallery = document.querySelector('.flexbox-container');
const galleryItems = document.getElementsByClassName('flexbox-item');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const userSearch = form.elements.query.value; // grab user input
const res = await getRequest(userSearch); // async func that returns a fully parsed Promise
tvShowMatches(res.data); // looks for matches, creates and appends name + image;
form.elements.query.value = '';
});
const getRequest = async (search) => {
const config = { params: { q: search } };
const res = await axios.get('http://api.tvmaze.com/search/shows', config);
return res;
};
const tvShowMatches = async (shows) => {
for (let result of shows) {
if (result.show.image) {
// new div w/ flexbox-item class + append to gallery
const tvShowMatch = document.createElement('DIV')
tvShowMatch.classList.add('flexbox-item');
gallery.append(tvShowMatch);
// create, fill & append tvShowName to tvShowMatch
const tvShowName = document.createElement('P');
tvShowName.textContent = result.show.name;
tvShowMatch.append(tvShowName);
// create, fill & append tvShowImg to tvShowMatch
const tvShowImg = document.createElement('IMG');
tvShowImg.src = result.show.image.medium;
tvShowMatch.append(tvShowImg);
}
}
};
Thanks
Instead of gallery.remove(galleryItems); consider resetting gallery.innerHTML to an empty string whenever a submit event occurs
Like this:
form.addEventListener('submit', async (e) => {
e.preventDefault();
gallery.innerHTML = ''; // Reset here
const userSearch = form.elements.query.value; // grab user input
const res = await getRequest(userSearch); // async func that returns a fully parsed Promise
tvShowMatches(res.data); // looks for matches, creates and appends name + image;
form.elements.query.value = '';
});
I believe this will do it.. you were close.
const galleryItems = document.getElementsByClassName('flexbox-item');
// to remove
galleryItems.forEach(elem => elem.remove() );

How do I use the variable out of its asynchronous function scope

I need to use the variable that I declared inside the asynchronous function out of that scope. Is that possible?
movies.forEach((movie) => {
const { title, poster_path, vote_average, overview, release_date, id } = movie
async function getCredits(url) {
const res = await fetch(url)
const data = await res.json()
const directors = [];
const directorsName = directors.join(", ") // The one I want to bring out of its scope
}
const CREDITS_URL = `the credits url goes here and it uses this -> ${id}`
getCredits(CREDITS_URL)
const directorsName = directors.join(", ") // Like this
const card = document.createElement("div")
card.innerHTML = `
<div class="director">
<h3>Directed by ${directorsName}</h3> // This is where I need the variable
</div>
`
cards.appendChild(card)
})
It's possible to return something from your async function. You can move your getCredits function out of the loop, and make the loop async, something like:
async function getCredits(url) {
const res = await fetch(url)
const data = await res.json()
const directors = [];
// Do something with data?
return directors.join(", ");
}
movies.forEach(async (movie) => {
const { id } = movie
const CREDITS_URL = `the movie url goes here and it uses this -> ${id}`
const response = await getCredits(CREDITS_URL);
const card = document.createElement("div")
card.innerHTML = `
<div class="director">
<h3>Directed by ${response}</h3> // This is where I need the variable
</div>
`
cards.appendChild(card)
});
If you'd like to have the DOM be created more quickly and have a sort of "lazy loading" of directors' names, you could do something like this:
movies.forEach((movie) => {
const { title, poster_path, vote_average, overview, release_date, id } = movie;
const card = document.createElement('div');
card.innerHTML = `<div class="director">
<h3>Directed by <span class='dname'>...</span></h3>
</div>`;
const dname = card.querySelector('.dname');
async function getCredits(url) {
const res = await fetch(url);
const data = await res.json();
const directors = [];
dname.textContent = directors.join(', ');
}
const CREDITS_URL = `the credits url goes here and it uses this -> ${id}`;
getCredits(CREDITS_URL);
cards.appendChild(card);
});
Here you're creating the div and appending it immediately, but with a ... for directors' names. But you put that part in a span and then set the textContent of the span once getCredits resolves.
*Edit: This also has the added side-benefit of preventing HTML injection from your director's return, by not inserting them using innerHTML.

Query JSON-based API with user input with just javascript

I've been watching tutorials on using JSON data and JS, decided to work with an API and make a simple APP. I ran into a snag and I'm not sure what's causing the issue. The issue is around the way I'm using user input to modify the query string. When I make my endpoint something static and get rid of the 'movieSearch' function,like this:
const movies = [];
const endpoint = 'http://www.omdbapi.com/?apikey=myAPIkey=batman';
fetch(endpoint)
.then(blob => blob.json())
.then(data => movies.push(...data.Search));
It works as desired, granted it's static.
My current code is:
const movies = [];
function movieSearch() {
const replace = this.value;
const endpoint = 'http://www.omdbapi.com/?apikey=myAPIkey=' + replace;
movies.length = 0;
fetch(endpoint)
.then(blob => blob.json())
.then(data => movies.push(...data.Search));
}
function findMatches(wordToMatch, movies) {
return movies.filter(film => {
const regex = new RegExp(wordToMatch, 'gi');
return film.Title.match(regex) || film.Year.match(regex)
})
}
function displayMatches() {
const matchArray = findMatches(this.value, movies);
const html = matchArray.map(film => {
const regex = new RegExp(this.value, 'gi');
const titleName = film.Title.replace(regex, `<span class="hl">${this.value}</span>`)
const yearName = film.Year.replace(regex, `<span class="hl">${this.value}</span>`)
return `
<li>
<span class="name">${titleName}, ${yearName}</span>
<span class="population">${film.imdbID}</span>
</li>
`;
}).join('');
suggestions.innerHTML = html;
}
const searchInput = document.querySelector('.search');
const suggestions = document.querySelector('.suggestions');
searchInput.addEventListener('keyup', displayMatches);
searchInput.addEventListener('change', displayMatches);
searchInput.addEventListener('keyup', movieSearch);
The displayMatches function starts acting funny and sometimes returns the list items and other times doesn't. I can't figure out what's causing it. Whichever way I call my endpoint my movies array looks the same, so I'm thoroughly confused.
Any suggestions? Is there a better way to do this?
My HTML is fairly simple right now:
<form class="search-form">
<input type="text" class="search" placeholder="Movies">
<ul class="suggestions">
<li>test1</li>
<li>test2</li>
</ul>
</form>
Thanks!
(I'm trying to do this all in JS)
Edit:
An example of the JSON data when searching batman with the API:
{"Search":[{"Title":"Batman Begins","Year":"2005","imdbID":"tt0372784","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BZmUwNGU2ZmItMmRiNC00MjhlLTg5YWUtODMyNzkxODYzMmZlXkEyXkFqcGdeQXVyNTIzOTk5ODM#._V1_SX300.jpg"},{"Title":"Batman v Superman: Dawn of Justice","Year":"2016","imdbID":"tt2975590","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BYThjYzcyYzItNTVjNy00NDk0LTgwMWQtYjMwNmNlNWJhMzMyXkEyXkFqcGdeQXVyMTQxNzMzNDI#._V1_SX300.jpg"},{"Title":"Batman","Year":"1989","imdbID":"tt0096895","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BMTYwNjAyODIyMF5BMl5BanBnXkFtZTYwNDMwMDk2._V1_SX300.jpg"},{"Title":"Batman Returns","Year":"1992","imdbID":"tt0103776","Type":"movie","Poster":"https://ia.media-imdb.com/images/M/MV5BOGZmYzVkMmItM2NiOS00MDI3LWI4ZWQtMTg0YWZkODRkMmViXkEyXkFqcGdeQXVyODY0NzcxNw##._V1_SX300.jpg"},{"Title":"Batman Forever","Year":"1995","imdbID":"tt0112462","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BNWY3M2I0YzItNzA1ZS00MzE3LThlYTEtMTg2YjNiOTYzODQ1XkEyXkFqcGdeQXVyMTQxNzMzNDI#._V1_SX300.jpg"},{"Title":"Batman & Robin","Year":"1997","imdbID":"tt0118688","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BMGQ5YTM1NmMtYmIxYy00N2VmLWJhZTYtN2EwYTY3MWFhOTczXkEyXkFqcGdeQXVyNTA2NTI0MTY#._V1_SX300.jpg"},{"Title":"The Lego Batman Movie","Year":"2017","imdbID":"tt4116284","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BMTcyNTEyOTY0M15BMl5BanBnXkFtZTgwOTAyNzU3MDI#._V1_SX300.jpg"},{"Title":"Batman: The Animated Series","Year":"1992–1995","imdbID":"tt0103359","Type":"series","Poster":"https://m.media-amazon.com/images/M/MV5BNzI5OWU0MjYtMmMwZi00YTRiLTljMDAtODQ0ZGYxMDljN2E0XkEyXkFqcGdeQXVyNTA4NzY1MzY#._V1_SX300.jpg"},{"Title":"Batman: Under the Red Hood","Year":"2010","imdbID":"tt1569923","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BYTdlODI0YTYtNjk5ZS00YzZjLTllZjktYmYzNWM4NmI5MmMxXkEyXkFqcGdeQXVyNTA4NzY1MzY#._V1_SX300.jpg"},{"Title":"Batman: The Dark Knight Returns, Part 1","Year":"2012","imdbID":"tt2313197","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BMzIxMDkxNDM2M15BMl5BanBnXkFtZTcwMDA5ODY1OQ##._V1_SX300.jpg"}],"totalResults":"344","Response":"True"}
Issues causing this behavior:
The movieSearch function is async and might not update the data in time.
The API sometimes return an error.
This solved by the code below, note that I moved everything into the fetch resolver making sure the search only executes when the API has responded.
Here is a JS Bin: https://jsbin.com/kicesivigu/1/edit?html,js,output
function findMatches(wordToMatch, movies) {
return movies.filter(film => {
console.log(film.Title, wordToMatch);
console.log(film.Title.toLowerCase().includes(wordToMatch));
return film.Title.toLowerCase().includes(wordToMatch) || film.Year.toLowerCase().includes(wordToMatch);
});
}
function displayMatches(movies, value) {
const matchArray = findMatches(value.toLowerCase(), movies);
const html = matchArray.map(film => {
const regex = new RegExp(value, 'gi');
const titleName = film.Title.replace(regex, `<span class="hl">${value}</span>`);
const yearName = film.Year.replace(regex, `<span class="hl">${value}</span>`);
return `
<li>
<span class="name">${titleName}, ${yearName}</span>
<span class="population">${film.imdbID}</span>
</li>
`;
}).join('');
suggestions.innerHTML = html;
}
const searchInput = document.querySelector('.search');
const suggestions = document.querySelector('.suggestions');
searchInput.addEventListener('keyup', () => {
const endpoint = 'https://www.omdbapi.com/?apikey=63f88e02&s=' + searchInput.value;
fetch(endpoint)
.then(blob => blob.json())
.then(data => {
console.log('response from API');
console.log(data);
if (!data.Error) displayMatches(data.Search, searchInput.value);
});
});

Categories

Resources