Javascript map in map await for responses - javascript

I have an array of questions.
Each question has some answers which are some files to upload.
Everything goes well instead of the fact that the API call is not waiting for Promise.all to finish.
Here are the steps:
map through questions array, if a question is image type, then get all the files and try to upload them.
after upload resolve all the promises from upload and add an answer to that question the result of Promise.all();
After the loop through all the questions is ready, make an API call to save into DB which now is not waiting to upload all the files and resolve everything in that array.
export function sendReview (taskId, companyId, questions, navigation) {
return async (dispatch) => {
dispatch(actions.sendReview.pending());
try {
let user = await getUser();
user = JSON.parse(user);
questions.map(async question => {
if (question.type === 'image') {
let images = question.answer;
if (images.length > 0) {
const results = images.map(async image => {
return await imageApi.upload(image).then(res => {
return res.url;
});
});
question.answer = await Promise.all(results).then(completed => {
return completed;
});
}
}
});
const data = await tasksApi.sendReview({
task_id: taskId,
company_id: companyId,
user_id: user.id,
questions: JSON.stringify(questions)
});
if (data.status === 201) {
markAsCompleted(taskId);
navigation.navigate('MyTasks');
dispatch(actions.sendReview.success({}));
}
else {
dispatch(actions.sendReview.error());
}
} catch (err) {
dispatch(actions.sendReview.error(err));
}
};
}
Here is the function used.
How can I make sure that all the items in .map() are ready and just after that make API call?

To give you an example from code I made quite some time ago:
await Promise.all((await readdir(repoPath, "utf8")).map(async file => {
if (!/\.mjs$/.test(file)) return;
const filePath = `${repoPath}/${file}`;
log(`importing "${file}"`);
const module = await import(filePath);
const meta = {
repository,
file,
filePath,
description: module.description || {}
};
module.default((...args) => createModule(meta, ...args));
}));
If you have asyncronous mapping handlers, you'll need to keep in mind, the content of the resulting map contains promises.
Promise.all() will help you with that.
In your case, all you need to do is change:
questions.map(async(question) => {
if(question.type === 'image'){
let images = question.answer;
if(images.length > 0){
const results = images.map(async (image) => {
return await imageApi.upload(image).then(res => {
return res.url;
});
});
question.answer = await Promise.all(results).then((completed) => {
return completed
});
}
}
});
as follows:
await Promise.all(questions.map(async(question) => {
if(question.type === 'image'){
let images = question.answer;
if(images.length > 0){
const results = await Promise.all(images.map(async (image) => {
return await imageApi.upload(image).then(res => {
return res.url;
});
}));
question.answer = results.then((completed) => {
return completed
});
}
}
}));

Use Promise.all to await promises in an array
Promise.all(questions.map(...))

Related

initialization in async reduce [duplicate]

This question already has answers here:
JavaScript array .reduce with async/await
(11 answers)
Closed 8 months ago.
const handleFileChange = async (e) => {
const target = e?.target?.files;
const attachments = await Array.from(target).reduce(async (acum, file) => {
file.id = uniqid();
// const format = file.name.split('.').pop();
// if (IMAGE_FORMATS.includes(format)) {
setIsLoading(true);
if (file.type.startsWith('image/')) {
const response = await channel.sendImage(file);
file.src = response.file;
acum.images.push(file);
} else {
const response = await channel.sendFile(file);
file.src = response.file;
acum.files.push(file);
}
setIsLoading(false);
return acum;
}, Promise.resolve({ files: [], images: [] }));
setFilesList(prev => {
console.log('files', [...prev, ...attachments.files]);
return [...prev, ...attachments.files];
});
setImagesList(prev => {
console.log('images', [...prev, ...attachments.images]);
return [...prev, ...attachments.images];
});
};
In the above code I got the following error
It looks it's cause by my initialization of array, but how should I address it?
An async function returns Promise, which makes it difficult to work with when using .reduce() as you would need to await your accumulator each iteration to get your data. As an alternative, you can create an array of Promises using the mapper function of Array.from() (which you can think of as using .map() directly after Array.from()). The idea here is that the map will trigger multiple asynchronous calls for each file by using sendImage/sendFile. These calls will run in parallel in the background. The value that we return from the mapping function will be a Promise that notifies us when the asynchronous call has successfully completed (once it resolves). Moreover, the mapping function defines what the promise resolves with, in our case that is the new object with the src property:
const isImage = file => file.type.startsWith('image/');
const filePromises = Array.from(target, async file => {
const response = await (isImage(file) ? channel.sendImage(file) : channel. sendFile(file));
return {...file, type: file.type, src: response.file};
});
Above filePromises is an array of Promises (as the async mapper function returns a Promise implicitly). We can use Promise.all() to wait for all of our Promises to resolve. This is faster than performing each asynchronous call one by one and only moving to the next once we've waited for the previous to complete:
setIsLoading(true); // set loading to `true` before we start waiting for our asynchronous work to complete
const fileObjects = await Promise.all(filePromises);
setIsLoading(false); // complete asynchronous loading/waiting
Lastly, fileObjects is an array that contains all objects, both files and images. We can do one iteration to partition this array into seperate arrays, one for images, and one for files:
const attachments = {files: [], images: []};
for(const fileObj of fileObjects) {
if(isImage(fileObj))
attachments.images.push(fileObj);
else
attachments.files.push(fileObj);
}
The reduce is not really necessary at this point:
Here is a solution with a map to transform the elements in promises and then Promise.all to wait for the exectution
const channel = {
sendImage: async (file) => {return {file}},
sendFile: async (file) => {return {file}}
}
const uniqid = () => Math.floor(Math.random() * 100);
const input = {
target: {
files: [{
src: 'src',
type: 'image/123'
},
{
src: 'src',
type: 'image/321'
},
{
src: 'src',
type: '123'
},
{
src: 'src',
type: '321'
}]
}
}
const setIsLoading = () => null;
const handleFileChange = async (e) => {
const target = e?.target?.files;
setIsLoading(true);
const attachments = {
images: [],
files: [],
}
await Promise.all(Array.from(target).map((file) => {
file.id = uniqid();
return new Promise(async (resolve) => {
if (file.type.startsWith('image/')) {
const response = await channel.sendImage(file);
file.src = response.file;
attachments.images.push(file);
} else {
const response = await channel.sendFile(file);
file.src = response.file;
attachments.files.push(file);
}
resolve();
});
}));
setIsLoading(false)
return attachments;
};
handleFileChange(input).then(res => console.log(res))

How to make two calls to two REST APIs correctly with javascript without using async await?

I need to make two calls to two rest api, in the first with one of the data obtained, pass it to the second url and ai obtain what I want, I could achieve it with the code below but my boss tells me that it is wrong, first it I did with asyn await and it told me not to use it, then later with fetch and axios but it is not well written, what would be the correct way to do it with both axios and fetch cases?
with axios
axios.all([axios.get(`${urlRestApi}`)]).then(
axios.spread(response1 => {
let arrPost = response1.data.map(res => {
return {
titulo: res.title.rendered,
contenido: res.content.rendered,
extracto: res.excerpt.rendered,
idImagen: res.featured_media,
};
});
console.log("AQUI", arrPost);
arrImg = arrPost.map(image => {
axios.get(`${urlImage}/${image.idImagen}`)
.then(urls => { // urls returns 10 objects each with a corresponding image
arrUrl.push(urls.data); //for this reason I put all 10 in an array, but it happens 10 times
if (arrUrl.length === 10) { // that's why when .length is 10 I go through it and assign what I want
let arrImage = arrUrl.map(img => {
return {
imagenes: img.source_url,
};
});
console.log("TEST", arrImage);
mostrarHTML(arrImage, arrPost); //I already have everything I run my function to print the data obtained
}
});
});
})
);
and with fetch
fetch(`${urlRestApi}`)
.then(respuesta => {
return respuesta.json();
})
.then(data => {
let arrPost = data.map(data => {
return {
titulo: data.title.rendered,
contenido: data.content.rendered,
extracto: data.excerpt.rendered,
idImagen: data.featured_media,
};
});
console.log(arrPost);
arrImg = arrPost.map(image => {
fetch(`${urlImage}/${image.idImagen}`)
.then(res => {
return res.json();
})
.then(urls => { // // urls returns 10 objects each with a corresponding image
arrUrl.push(urls); //for this reason I put all 10 in an array, but it happens 10 times
if (arrUrl.length === 10) { // that's why when .length is 10 I go through it and assign what I want
arrImage = arrUrl.map(image => {
return {
imagenes: image.source_url,
};
});
console.log("aqui", arrImage);
mostrarHTML(arrImage, arrPost); //I already have everything I run my function to print the data obtained
}
});
});
})
.catch(error => {
console.log(error);
});
With fetch, without async/await:
fetch(urlRestApi)
.then((respuesta) => respuesta.json())
.then((data) => {
const posts = data.map((data) => ({
titulo: data.title.rendered,
contenido: data.content.rendered,
extracto: data.excerpt.rendered,
idImagen: data.featured_media,
}));
// Create an array of promises that fetch image data and combines it
// with the original post.
const imagePostPromises = posts.map((post) => {
return fetch(`${urlImage}/${image.idImagen}`)
.then((res) => res.json())
.then((imageData) => ({
// Combine the original post with the image data fetched
...post,
imageData,
}));
});
// Return a promise that resolves only when all of the `imagePostPromises` have finished.
return Promise.all(imagePostPromises);
})
.then((postsWithImages) => {
console.log(postsWithImages);
});
And, much more readably, if only you could use async/await,
async function doThings() {
const respuesta = await fetch(urlRestApi);
const data = await respuesta.json();
const posts = data.map((data) => ({
titulo: data.title.rendered,
contenido: data.content.rendered,
extracto: data.excerpt.rendered,
idImagen: data.featured_media,
}));
const imagePostPromises = posts.map(async (post) => {
const res = await fetch(`${urlImage}/${image.idImagen}`);
const imageData = await res.json();
// Combine the original post with the image data fetched
return ({
...post,
imageData,
});
});
const postsWithImages = await Promise.all(imagePostPromises);
console.log(postsWithImages);
}

Use of promises in NODE.js // console log prints the result several times

I'm making a program that takes an array of links and returns how many are broken and how many are working. Right now, I'm testing it with an array that has four working links and two broken links. Here's my code:
function getBrokenLinks(linksArr){
let links = linksArr
let brokenLinks = 0
links.forEach(link => {
fetch(link.href)
.then( res => {
if ( res.status != 200 ){
brokenLinks++
}
}).then( () => {console.log(brokenLinks)})
})
return brokenLinks
}
and this is the output i receive:
output
I want the console to print the total of broken links only once, and after it has completed fetching all the links.
You need to wait for all promises first. Then you can print the result. Also, to return anything, you need to make the function async and then all your outer code must also be async!
async function getBrokenLinks (linksArr) {
let brokenLinks = 0
await Promise.all(linksArr.map(link => (async () => {
try {
const res = await fetch(link.href)
if (res.status != 200) brokenLinks++
} catch (e) {
brokenLinks++
}
})()))
console.log(brokenLinks)
return brokenLinks
}
You can use Promise.all to wait for all promises to be resvoled:
/*
Promise.all([promise1, promise2,..])
.then(function() {
// all promises have been resolved
})
*/
function getBrokenLinks(linksArr) {
let links = linksArr
let brokenLinks = 0
let promises = []
links.forEach(link => {
// save promise to push onto array
let promise = fetch(link.href)
.then(res => {
if (res.status != 200) {
brokenLinks++
}
})
promises.push(promise)
})
return Promise.all(promises)
.then(() => {
return brokenLinks
})
}
// Calling code:
/*
getBrokenLinks([])
.then(console.log)
*/

How to run promise based function serially

I am trying to run the node js Lighthouse function serially (one at a time) with an array of URLs. My problem is that whenever I loop through the array, Lighthouse runs all the URLs at once, which I imagine is problematic if you have a very large array of URLs.
The code:
for(let url of urls) {
function launchChromeAndRunLighthouse(url, opts, config = null) {
return chromeLauncher.launch({chromeFlags: opts.chromeFlags}).then(chrome => {
opts.port = chrome.port;
return lighthouse(url, opts, config).then(results => {
return chrome.kill().then(() => results.lhr)
});
});
}
}
launchChromeAndRunLighthouse('https://example.com', opts).then(results => {
// Use results!
});
Please help! And thank you for your time!
Your answer is correct but it can be improved. Since you have access to async and await, you should fully utilize it to make your code cleaner:
async function launchChromeAndRunLighthouse (url, opts, config = null) {
const chrome = await chromeLauncher.launch({chromeFlags: opts.chromeFlags});
opts.port = chrome.port;
const { lhr } = await lighthouse(url, opts, config);
await chrome.kill();
return lhr;
}
async function launchAudit (urls) {
for (const url of urls) {
const results = await launchChromeAndRunLighthouse(url, opts);
// Use results!
};
}
launchAudit(urls);
I believe I figured it out. What I did is below. Please continue to send feedback if you think this is wrong.
function launchChromeAndRunLighthouse(url, opts, config = null) {
return chromeLauncher.launch({chromeFlags: opts.chromeFlags}).then(chrome => {
opts.port = chrome.port;
return lighthouse(url, opts, config).then(results => {
return chrome.kill().then(() => results.lhr)
});
});
};
async function launchAudit(urls) {
for (let url of urls) {
await launchChromeAndRunLighthouse(url, opts).then(results => {
// Use results!
});
};
};
launchAudit(urls);
A variation on Patric Roberts answer (which should be the accepted answer).
I was wondering if it was necessary to kill chrome every iteration.
const lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');
function launchChromeAndRunLighthouse(sites, opts, config = null) {
return chromeLauncher.launch({chromeFlags: opts.chromeFlags}).then(chrome => {
opts.port = chrome.port;
const siteResults = [];
return new Promise((resolve, reject) => {
// batch async functions.
// C/O https://stackoverflow.com/questions/43082934/how-to-execute-promises-sequentially-passing-the-parameters-from-an-array
const runBatch = async (iterable, action) => {
for (const x of iterable) {
await action(x)
}
}
// func to run lighthouse
const doLightHouse = (site) => new Promise((resolve, reject) => {
lighthouse(site, opts, config).then(results => {
siteResults.push(results.lhr);
resolve();
});
});
// go go go
runBatch(sites, doLightHouse).then(d => {
chrome.kill().then((result) => {
resolve(siteResults)
})
});
});
});
}
const opts = {
chromeFlags: ['--show-paint-rects'],
onlyCategories: ['performance']
};
const sites = ['https://www.example.com', 'https://www.test.com']
launchChromeAndRunLighthouse(sites, opts).then(results => {
// Use results!
console.log(results);
});
Just to execute your code as test, we'll use async/await and IIFE
Then, will create function which will put all our request to array of non resolved promises, so we could use it with Promise.all()
You need to rewrite code in something like this:
(async() => {
const promisesToExecute = [];
const launchChromeAndRunLighthouse = async (url, opts, config = null) => {
const chrome = await return chromeLauncher.launch({chromeFlags: opts.chromeFlags});
opts.port = chrome.port;
promisesToExecute.push(lighthouse(url, opts, config));
}
const results = await Promise.all(promisesToExecute);
for(const result of results) {
const resolvedResult = await result.kill();
// here you can access your results.lhr
console.log(resolvedResult.lhr);
}
})()
Please note, this code wasn't tested, so there might be problems with kill() on result. But, the main goal is to answer your question and explain how to execute promises.
Also, if you don't want to execute all promises at the same time, you could use Promise.waterfall with some npm package, like this

Variable Retains Original Value Despite Having New Value Earlier and Further within a Function

let getProjects = function() {
try {
return axios.get('https://app.asana.com/api/1.0/projects/')
} catch (error) {
console.error(error)
}
}
let getTasks = function(project) {
try {
return axios.get('https://app.asana.com/api/1.0/projects/'+project+'/tasks')
} catch (error) {
console.error(error)
}
}
async function getAsanaData() {
let projects = await getProjects()
projects = projects.data.data
projects.map(async (project) => {
//project.attachments = []
let tasks = await getTasks(project.gid)
if(tasks != undefined){
tasks = tasks.data.data
project.tasks = tasks
//console.log(projects)
}
})
console.log(projects)
return projects
}
Promise.try(() => {
return getAsanaData();
}).then((result) => {
//console.log(util.inspect(result, {showHidden: false, depth: null}))
//var asanaData = safeJsonStringify(result);
//fs.writeFile("thing.json", asanaData);
})
.catch(err=>console.log(err))
In getAsanaData(), projects has a new value after project.tasks = tasks.
However, it's original value is printed by console.log(projects) before return projects.
This of course also means that the original value rather than the necessary new value will be returned.
What is the cause and how do I resolve this?
Try this:
async function getAsanaData() {
let projects = await getProjects()
return Promise.all(projects.data.data.map(async (project) => {
let tasks = await getTasks(project.gid)
project.tasks = !!tasks ? tasks.data.data : project.tasks
return project
}))
}
This will go through the async calls to getTasks and return for each the project. Then you should get them all resolved via Promise.all

Categories

Resources