How can I add async await to this function - javascript

export function fetchNews(data) {
const news = []
var numOfArticlesArray = fetchNewsPreprocessing(data)
data.map((interest, index) => {
fetch(`https://newsapi.org/v2/top-headlines?country=us&category=${interest}&apiKey=`)
.then(res => res.json())
.then(res => res.articles)
.then(res => {
for (let i = 0; i < numOfArticlesArray[index]; i++) {
news.push(res[i])
}
})
.catch(err => console.log(err))
})
console.log(news);
}
So here is the function, my issue is that I'm getting this console.log(news); before I finish appending to my news array in here news.push(res[i]) which results in a blank array.
I tried adding async and await to the function like this async function fetchNews(data) and await data.map((interest, index) => { but no use.
thanks in advance.

Do you want to execute your fetch() calls serially, or in parallel?
If you want to execute them serially then something like this will work:
export function fetchNews(data) {
const news = [];
const numOfArticlesArray = fetchNewsPreprocessing(data);
data.map( async (interest, index) => {
const url = `https://newsapi.org/v2/top-headlines?country=us&category=${interest}&apiKey=`;
try {
const res = await fetch(url).then(res => res.json());
const articles = res.articles;
for ( let i = 0 ; i < numOfArticlesArray[index] ; i++ ) {
news.push(articles[i]);
}
} catch (err) {
console.log(err);
}
})
console.log(news);
}
If you want to execute them in parallel, however, then something like this is what you want:
export async function fetchNews(data) {
const news = [];
const numOfArticlesArray = fetchNewsPreprocessing(data);
const requests = data.map( (interest, index) => {
const url = `https://newsapi.org/v2/top-headlines?country=us&category=${interest}&apiKey=`;
const res = fetch(url).then(res => res.json());
return res;
})
const responses = await Promise.all( requests );
for ( const i = 0 ; i < responses.length ; ++i ) {
const res = responses[i];
const articles = res.articles;
for ( let j = 0 ; j < numOfArticlesArray[i] ; ++j ) {
news.push(articles[j]);
}
}
console.log(news);
}

You should put await in front of fetch() instead. For example, this piece of code will output the news array with the test element:
async function fetchNews(data) {
let news = [];
await fetch(url).then(() => news.push('test'));
console.log(news)
}

Related

How to modify to promise.all JS

I'm new to javascript, and I'm trying to make some changes.
I have a search code in searchBar and with it a call to an API.json to fetch the information and put it on the site. Everything works fine, however I would like to modify it to add a Promise.all and be able to call more than one API and be able to display it on the site. But all my attempts have failed.
what do i have in js:
const localList = document.getElementById('localList');
const searchBar = document.getElementById('searchBar');
let hpWords = [];
searchBar.addEventListener('keyup', (e) => {
const searchString = e.target.value.toLowerCase();
const filteredWords = hpWords.filter((words) => {
return (
words.name.toLowerCase().includes(searchString)
);
});
displayCharacters(filteredWords);
});
const loadCharacters = async () => {
try {
const res = await fetch('https://duhnunes.github.io/api/local.json');
hpWords = await res.json();
displayCharacters(hpWords);
} catch (err) {
console.error(err);
}
};
const displayCharacters = (words) => {
const htmlString = words
.map((character) => {
return `
<div class="voc-box-content">
<h5>${character.name} - ${character.trans} [${character.type} - ${character.type2}]</h5>
<p>${character.description}</p>
</div>
`;
})
.join('');
localList.innerHTML = htmlString;
};
loadCharacters();
To display on the page I am using <div id="localList"></div>. In the example I am displaying local.json and I would also like to display item.json with <div id ="itemList"></div>.
Both have the same display structure on the <div class="voc-box-content">...</div> site.
Cheers,
DuH
To fetch words from multiple apis you can use this:
const searchBar = document.getElementById('searchBar');
const lists = {
localList: {
api: 'api1'
},
miscList: {
api: 'api2'
},
...
};
Object.entries(lists).forEach(([id, list]) => {
list.element = document.getElementById(id);
});
searchBar.addEventListener('keyup', (e) => {
const searchString = e.target.value.toLowerCase();
Object.values(lists).forEach((list) => {
list.filteredWords = list.words.filter((word) => {
return word.name.toLowerCase().includes(searchString);
});
});
displayCharacters();
});
const loadCharacters = async () => {
try {
await Promise.all(
Object.values(lists).map(async (list) => {
const response = await fetch(list.api);
list.words = await response.json();
})
);
displayCharacters();
} catch (err) {
console.error(err);
}
};
const displayCharacters = () => {
Object.values(lists).forEach((list) => {
list.element.innerHTML = (list.filteredWords ?? list.words)
.map((word) => `
<div class="voc-box-content">
<h5>${word.name} - ${word.trans} [${word.type} - ${word.type2}]</h5>
<p>${word.description}</p>
</div>`)
.join('');
});
};
loadCharacters();

I am facing problem chaining the async code in Javascript

I am trying to execute allCountryData and return a promise its working fine but after allCountryData is done executing I want to perform a operation on that returned data / or allCountryDataArray and store the highest values in arrayOfHighestCases
Note I can't chain the other login in allCountryData.
Please help let me know if you need any more details
export const allCountryDataArray = [];
export const arrayOfHighestCases = [];
const allCountryData = async () => {
sendHTTP()
.then((res) => {
return res.response;
})
.then((res) => {
allCountryDataArray.push(...res);
return allCountryDataArray;
});
return await allCountryDataArray;
// Highest Cases
};
The code is below is not working
const highestCasesData = async () => {
// const allCountryDataArrayy = await allCountryData();
// allCountryData()
// .then((data) => {
// console.log(arrayOfHighestCases[0]);
// })
// .then((res) => {
const np = new Promise((res, rej) => {
res(allCountryData());
});
return np.then((res) => {
console.log(res);
const arrayofHigh = allCountryDataArray.sort((a, b) => {
if (a.cases.total < b.cases.total) {
return 1;
} else if (a.cases.total > b.cases.total) {
return -1;
} else {
return 0;
}
});
console.log(arrayofHigh);
const slicedArray = arrayofHigh.slice(0, 6);
for (const eachHighCase of slicedArray) {
arrayOfHighestCases.push(eachHighCase);
}
console.log(arrayOfHighestCases);
return arrayOfHighestCases;
});
// });
};
highestCasesData();
Filling global arrays with async data is a way into timing conflicts. Bugs where the data ain't there, except when you look it is there and yet another question here on my SO about "Why can't my code access data? When I check in the console everything looks fine, but my code ain't working."
If you want to store something, store Promises of these arrays or memoize the functions.
const allCountryData = async () => {
const res = await sendHTTP();
return res.response;
};
const highestCasesData = async () => {
const allCountryDataArray = await allCountryData();
return allCountryDataArray
.slice() // make a copy, don't mutate the original array
.sort((a, b) => b.cases.total - a.cases.total) // sort it by total cases DESC
.slice(0, 6); // take the first 6 items with the highest total cases
}
This is working please let me know if I can make some more improvements
const allCountryData = async () => {
return sendHTTP()
.then((res) => {
return res.response;
})
.then((res) => {
allCountryDataArray.push(...res);
return allCountryDataArray;
});
// Highest Cases
};
const highestCasesData = async () => {
return allCountryData().then((res) => {
console.log(res);
const arrayofHigh = allCountryDataArray.sort((a, b) => {
if (a.cases.total < b.cases.total) {
return 1;
} else if (a.cases.total > b.cases.total) {
return -1;
} else {
return 0;
}
});
console.log(arrayofHigh);
const slicedArray = arrayofHigh.slice(0, 6);
for (const eachHighCase of slicedArray) {
arrayOfHighestCases.push(eachHighCase);
}
console.log(arrayOfHighestCases);
return arrayOfHighestCases;
});
};
highestCasesData();

Promise.all() to await the return of an object property

Inside an async function i have a loop and inside this loop i need to use await to resolve a promise from another async function.
async function smallestCities(states) {
const citiesInState = [];
for (const state of states) {
const length = await lengthOfState(state.Sigla);
const stateObject = {
state: state.Sigla,
cities: length,
};
citiesInState.push(stateObject);
}
citiesInState.sort((a, b) => {
if (a.cities > b.cities) return 1;
if (a.cities < b.cities) return -1;
return 0;
});
return citiesInState.filter((_, index) => index < 5).reverse();
}
It's work fine, but eslint says to disallow await inside of loops and use Promise.all() to resolve all promises.
The problem is that my promises are in an object property:
How can i figure out to use Promise.all() with properties of an object?
Chain a .then onto the lengthOfState call to make the whole Promise resolve to the object you need, inside the Promise.all:
const citiesInState = await Promise.all(
states.map(
state => lengthOfState(state.Sigla).then(cities => ({ state: state.Sigla, cities }))
)
);
const NEW_LAND = 'newLand'
const ACTUAL_FINLAND = 'actualFinland'
const PIRKKAS_LAND = 'pirkkasLand'
const STATE_CITY_MAP = {
[NEW_LAND]: ['HELSINKI', 'VANTAA', 'KORSO'],
[ACTUAL_FINLAND]: ['TURKU'],
[PIRKKAS_LAND]: ['WHITE RAPIDS', 'NOKIA'],
}
const mockGetCities = (stateName) => new Promise((res) => {
setTimeout(() => { res([stateName, STATE_CITY_MAP[stateName]]) }, 0)
})
const compareStatesByCityQty = (a, b) => {
if (a[1].length > b[1].length) return 1
if (a[1].length < b[1].length) return -1
return 0
}
const getSmallestStates = async (stateNames, cityQty) => {
const cities = await Promise.all(stateNames.map(mockGetCities))
return cities
.sort(compareStatesByCityQty)
.reverse()
.slice(0, cityQty)
}
;(async () => {
const stateNames = [NEW_LAND, ACTUAL_FINLAND, PIRKKAS_LAND]
const smallestStates = await getSmallestStates(stateNames, 2)
console.log(smallestStates)
})()

Chain of async calls where the last call compares something from the first

I need to complete a number of fetch requests where each relies on the successful completion of the last. For the final request, I need to compare a value from the first request.
Here's what I've got, and it appears to work, but I'm not sure if I have this 100% correct.
const getData = async ( id ) => {
return await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
.then( json => json.json() )
.then( res => res )
}
const res_1 = await getData( 1 ).then( res => res )
const res_2 = await getData( res_1.id + 1 ).then( res => res )
const res_3 = await getData( res_2.id + 1 ).then( res => res )
const res_4 = await getData( res_3.id + 1 ).then( res => res )
console.log(`RES_1: \n${JSON.stringify(res_1)}`)
console.log(`RES_2: \n${JSON.stringify(res_2)}`)
console.log(`RES_3: \n${JSON.stringify(res_3)}`)
console.log(`RES_4: \n${JSON.stringify(res_4)}`)
if ( res_1.id !== res_4.id ) {
console.log("Id's don't match")
} else {
console.log("Id's match")
}
You may ignore the logging, it's simply to visualize what's happening.
You don't need additional .then( res => res ) as it just returning the same object back again. Also, as you are using async/await you can remove .then(...) and just await on the result of the promise. Also, we can use await inside an async function only, so you can put all your logic inside an async function fn like:
const getData = async(id) => {
const res = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
return res.json();
}
async function fn() {
const res_1 = await getData(1)
const res_2 = await getData(res_1.id + 1)
const res_3 = await getData(res_2.id + 1)
const res_4 = await getData(res_3.id + 1)
console.log('res_1.id: ', res_1.id);
console.log('res_4.id: ', res_4.id);
if (res_1.id !== res_4.id) {
console.log("Id's don't match")
} else {
console.log("Id's match")
}
}
fn();
Or, you can also use immediately-invoked async function expressions for this purpose like:
const getData = async(id) => {
const res = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
return res.json();
}
(async function() {
const res_1 = await getData(1)
const res_2 = await getData(res_1.id + 1)
const res_3 = await getData(res_2.id + 1)
const res_4 = await getData(res_3.id + 1)
console.log('res_1.id: ', res_1.id);
console.log('res_4.id: ', res_4.id);
if (res_1.id !== res_4.id) {
console.log("Id's don't match")
} else {
console.log("Id's match")
}
}());

Loop through async requests with nested async requests

I have a scenario where I am calling an API that has pagination.
What I'd like to do is the following, 1 page at a time.
Call API Page 1
For each of the items in the response, call a Promise to get more data and store in an array
Send the array to an API
Repeat until all pages are complete
What I currently have is the following, however I think I am possibly complicating this too much, although unsure on how to proceed.
export const importData = async() {
const pSize = 15;
const response = await getItems(pSize, 1);
const noPage = Math.ceil(response.totalMerchandiseCount/pSize);
for (let i = 1; i < noPage; i++) {
const items = [];
const data = await getItems(pSize, i);
await async.each(data.merchandiseList, async(i, cb) => {
const imageURL = await getImageURL(i.id, i.type);
items.push({
id: i.id,
imageURL: imageURL,
});
cb();
}, async() => {
return await api.mockable('sync', items);
});
}
}
export const getImageURL = async(id, type) => {
let url = `https://example.com/${id}`;
return axios.get(url)
.then((response) => {
const $ = cheerio.load(response.data);
// do stuff to get imageUrl
return image;
})
.catch((e) => {
console.log(e);
return null;
})
};
The issue I have at the moment is that it seems to wait until all pages are complete before calling api.mockable. Items is also empty at this point.
Can anyone suggest a way to make this a bit neater and help me get it working?
If this is all meant to be serial, then you can just use a for-of loop:
export const importData = async() {
const pSize = 15;
const response = await getItems(pSize, 1);
const noPage = Math.ceil(response.totalMerchandiseCount/pSize);
for (let i = 1; i < noPage; i++) { // Are you sure this shouldn't be <=?
const items = [];
const data = await getItems(pSize, i);
for (const {id, type} of data.merchandiseList) {
const imageURL = await getImageURL(id, type);
items.push({id, imageURL});
}
await api.mockable('sync', items);
}
}
I also threw some destructuring and shorthand properties in there. :-)
If it's just the pages in serial but you can get the items in parallel, you can replace the for-of with map and Promise.all on the items:
export const importData = async() {
const pSize = 15;
const response = await getItems(pSize, 1);
const noPage = Math.ceil(response.totalMerchandiseCount/pSize);
for (let i = 1; i < noPage; i++) { // Are you sure this shouldn't be <=?
const data = await getItems(pSize, i);
const items = await Promise.all(data.merchandiseList.map(async ({id, type}) => {
const imageURL = await getImageURL(id, type);
return {id, imageURL};
}));
await api.mockable('sync', items);
}
}
That async function call to map can be slightly more efficient as a non-async function:
export const importData = async() {
const pSize = 15;
const response = await getItems(pSize, 1);
const noPage = Math.ceil(response.totalMerchandiseCount/pSize);
for (let i = 1; i < noPage; i++) {
const data = await getItems(pSize, i);
const items = await Promise.all(data.merchandiseList.map(({id, type}) =>
getImageURL(id, type).then(imageURL => ({id, imageURL}))
));
await api.mockable('sync', items);
}
}

Categories