I'm trying to prefetch multiple image before navigating to another screen, but returnedStudents all undefined.
prepareStudentImages = async (students) => {
let returnedStudents = students.map(student => {
Image.prefetch(student.image)
.then((data) => {
...
})
.catch((data) => {
...
})
.finally(() => {
return student;
});
});
await console.log(returnedStudents); // ----> all items undefined
}
There are a couple of things to fix with this:
1) Your map() function does not return anything. This is why your console log is undefined.
2) Once your map functions work, you are logging an array of promises. To deal with multiple promises (an array), you can use Promise.all().
So I think to fix this, you can do:
prepareStudentImages = async (students) => {
const returnedStudents = students.map(student =>
Image.prefetch(student.image)
.then((data) => {
...
})
.catch((data) => {
...
})
.finally(() => {
return student
})
)
console.log(returnedStudents) // log the promise array
const result = await Promise.all(returnedStudents) // wait until all asyncs are complete
console.log(result) // log the results of each promise in an array
return result
}
Related
I have a list of addresses pointing to json resources. I want to download those files to be able to use them in later processing.
I have this piece of code that uses the fetch() method:
let urlList = [
"https://url.to/resource1.json",
"https://url.to/resource2.json"
];
let promiseList = [];
let jsonBaseList = [];
urlList.forEach (function (url, i) {
promiseList.push (
fetch(url).then (function (res) {
jsonBaseList[i] = res.json();
})
);
});
Promise
.all(promiseList)
.then (function () {
console.log('All done.');
})
console.log('jsonBaseList: ', jsonBaseList)
Thus, the jsonBaseList contains a list of promises.
But I just want a list of json objects instead.
It's possible?
You should return that res.json() and use the resolved value in the next .then, since Response.json() returns another promise.
let urlList = [
"https://url.to/resource1.json",
"https://url.to/resource2.json"
];
let promiseList = [];
let jsonBaseList = [];
urlList.forEach (function (url, i) {
promiseList.push (
fetch(url).then (function (res) {
return res.json();
}).then(function (res) {
jsonBaseList[i] = res;
})
);
});
Promise
.all(promiseList)
.then (function () {
console.log('All done.');
})
console.log('jsonBaseList: ', jsonBaseList)
Update: I just edited your current code in order to make it work. But you can write it better:
let urlList = [
"https://url.to/resource1.json",
"https://url.to/resource2.json"
];
let jsonBaseList = [];
const promiseList = urlList.map((url) => {
return fetch(url)
.then(response => response.json())
})
Promise.all(promiseList).then(values => {
jsonBaseList = values;
console.log('All done.');
})
console.log('jsonBaseList: ', jsonBaseList)
Update: The console.log at the end of the code will output an empty array since promises are run asynchronously after the current script is run. So you can:
Put it inside .then
or put it in a chained .then
or use the async/await syntax (a cleaner way to write promises)
(async function() {
let urlList = [
"https://url.to/resource1.json",
"https://url.to/resource2.json"
];
const promiseList = urlList.map((url) => {
return fetch(url)
.then(response => response.json())
})
const jsonBaseList = await Promise.all(promiseList)
console.log('All done.');
console.log('jsonBaseList: ', jsonBaseList)
})()
Thus, the jsonBaseList contains a list of promises.
Yes, because res.json() returns a promise. But as of when you show your console.log, jsonBaseList will be [] because that code runs before any of the promises settle.
The minimal change to your code for jsonBaseList to have values in it in code run after the promises settle is:
// ...
// ...
let urlList = [
"https://url.to/resource1.json",
"https://url.to/resource2.json"
];
let promiseList = [];
let jsonBaseList = [];
urlList.forEach (function (url, i) {
promiseList.push (
fetch(url) .then (function (res) {
return res.json();
})
.then (function (value) { // ***
jsonBaseList[i] = value; // *** This is what I added
}) // ***
);
});
Promise
.all(promiseList)
.then (function () {
console.log('All done.'); // *** Use `jsonBaseList` here
})
// Removed the `console.log` here that would have logged `[]`
but it can be much simpler; all of the above can be replaced with:
let urlList = [
"https://url.to/resource1.json",
"https://url.to/resource2.json"
];
Promise.all(urlList.map(url => fetch(url).then(response => response.json())))
.then(jsonBaseList => {
// ...use `jsonBaseList` here...
})
.catch(error => {
// ...handle/report error here...
});
Notice that you use jsonBaseList in the then handler on Promise.all. I don't declare it in a broader scope because it's not filled in until that handler is called (that's the reason your console.log at the end will always log []). If you declare it in a broader scope, you make it likely you'll try to use it before it's available (as in the question's code).
But if want it in a broader scope and you realize it won't be filled in until later, add:
let jsonBaseList = []; // *** Not filled in until the promises settle!
and then change
.then(jsonBaseList => {
// ...use `jsonBaseList` here...
})
to
.then(list => {
jsonBaseList = list;
})
(Or use const and jsonBaseList.push(...list).)
Side note: You probably want to handle the possibility that the HTTP request failed (even though the network request succeeded — this is a footgun in the fetch API I write about here, it doesn't rject on HTTP failure, just network failure). So:
let urlList = [
"https://url.to/resource1.json",
"https://url.to/resource2.json"
];
Promise.all(
urlList.map(
url => fetch(url).then(response => {
if (response.ok) {
throw new Error(`HTTP error ${response.status}`);
}
return response.json();
})
)
)
.then(jsonBaseList => {
// ...use `jsonBaseList` here...
})
.catch(error => {
// ...handle/report error here...
});
You need to wait for your promises to be done first
await Promise.all(promiseList)
console.log('jsonBaseList: ', jsonBaseList)
A question can be found here:
Implement the getInParallel function that should be used to invoke
multiple API calls in parallel. The function accepts an array of
functions that return a Promise. The function should return a Promise
which should resolve to an array of results from the apiCalls
argument.
For example, calling the following code should print [ 'First API call!', 'Second API call!' ]:
let promise = getInParallel([() => Promise.resolve("First API call!"),
() => Promise.resolve("Second API call!")]);
if(promise) {
promise.then((result) => console.log(result)).catch((err) => console.log(err));
}
This is my solution:
function getInParallel(apiCalls) {
// extract function one by one from array
// then return each function's Promise to 'rFunt' as Promise.all only accepts array, iterable of promises as an input
let rFunt = apiCalls.map((funct) => {
return funct()
})
return Promise.all(rFunt).then((res) => {
return res
})
}
let promise = getInParallel([() => Promise.resolve("First API call!"),
() => Promise.resolve("Second API call!")]);
if (promise) {
promise.then((result) => console.log(result)).catch((err) => console.log(err));
}
module.exports.getInParallel = getInParallel;
Are there any other ways/concept to solve this?
Your answer is pretty straight-forward, and maybe the simplest one.
I will rewrite getInParallel as below (usually we need type checking, omited here)
const parallel = funcs => Promise.all(funcs.map(fn => fn()));
Your solution is pretty close yet needs some modifications. Please check the code below:
function getInParallel(apiCalls) {
// Write your code here
let functions = apiCalls.map(apiCall => apiCall());
return Promise.all(functions).then(response => response);
}
let promise = getInParallel([
() => Promise.resolve("First API call!"),
() => Promise.resolve("Second API call!")
]);
if (promise) {
promise.then((result) => console.log(result)).catch((err) => console.log(err));
}
Here is a clean way to go about it
function getInParallel(apiCalls) {
// extract function one by one from array
// then return each function's Promise to 'rFunt' as Promise.all only accepts array, iterable of promises as an input
let resFunt = apiCalls.map((funct) => {
return funct()
})
return Promise.all(resFunt).then((res) => {
return res
})
}
let promise = getInParallel([() => Promise.resolve("First API call!"),
() => Promise.resolve("Second API call!")]);
if (promise) {
promise.then((result) => console.log(result)).catch((err) => console.log(err));
}
module.exports.getInParallel = getInParallel;
Here is the short way.
function getInParallel(apiCalls) {
return Promise.all(apiCalls.map(f=>f()));
}
let promise = getInParallel([() => Promise.resolve("First API call!"),
() => Promise.resolve("Second API call!")]);
if(promise) {
promise.then((result) => console.log(result)).catch((err) => console.log(err));
}
module.exports.getInParallel = getInParallel;
I have this two functions, writedb is supposed to act only when uploadImages is done. I'm not getting what I'm hoping for.
The resolve shows "null" for "this.imageURL" as is predefined but is triggered before the console.log that are inside the loop which show the correct wanted information.
uploadImages() {
return new Promise(async (resolve, reject) => {
let newImageURL = []
for (const path of this.images){
fs.readFile(path,
async (err, data) => {
const ref = st.ref(`images/${this.id}/${path.split('/').pop()}`);
await ref.put(data);
let url = await ref.getDownloadURL();
newImageURL.push(url);
this.imageURL = newImageURL;
console.log(this.imageURL);
})
}
resolve(console.log(this.name),
console.log(this.imageURL),
console.log('Done upload images'))
})
}
writedb(){
(() => {
let obj = {};
this.uploadImages().then(
console.log('writedb in action...'),
db.collection("products").doc(this.id).set(Object.assign(obj, this))
)
})();
}
What am I doing wrong here? How can i get the promise to resolve only if the for loop is done??
Use fs.promises and Promise.all to wait for each readFile to resolve - and avoid the explicit Promise construction antipattern:
uploadImages() {
return Promise.all(this.images.map(
path => fs.promises.readFile(async (data) => {
const ref = st.ref(`images/${this.id}/${path.split('/').pop()}`);
await ref.put(data);
return ref.getDownloadURL();
})
));
}
Then instance.uploadImages() will return a Promise that resolves to an array of the data you want. uploadImages().then((result) => { /* do stuff with result */ }).catch(handleErrors);
With CertainPerformance help I've corrected my code as follows
Thank you! After some though I finally was able to implement it and achieve the expected result.
uploadImages (){
return Promise.all(this.imagespath.map(
path => fs.promises.readFile(path)
.then(async (data)=>{
const ref = st.ref(`images/${this.id}/${path.split('/').pop()}`);
await ref.put(data);
return ref.getDownloadURL();
})
))
}
writedb(){
this.uploadImages()
.then((res)=>{
this.imageURL = res;
//Firebase no toma custom objects Object.assign(obj, this)
let obj ={}
Object.assign(obj, this);
delete obj.imagespath;
db.collection("products").doc(this.id).set(obj)
}).catch( err=>console.log(err) )
}
I have a collection of promises or async functions, I need to manipulate slightly and then execute in parallel.
The issue I am having is that I am unable to resolve the promises.
// Some dummy tasks:
const taskA = () => Promise.resolve(500);
const taskB = () => {
return new Promise(resolve => resolve(300));
};
// Push them into an array:
const tasks = [];
const registerTask = (name, task) => {
tasks.push( async () => {
return { [name]: await task() };
});
};
// trying to get the results
const runTasks = () => {
const result = Promise.all(tasks).then(results => results);
return result;
}
// usage
registerTask('taskA', taskA);
registerTask('taskB', taskB);
console.log(runTasks())
Following the successful resolution of promises ideally I would also like to handle errors individually for each task.
The problem is that your registerTask function pushes functions onto the tasks array, not Promise objects. If you change the function like this, it should work:
const registerTask = (name, task) => {
const asyncFunc = async () => {
return { [name]: await task() };
};
tasks.push( asyncFunc() ); // This pushes a promise into 'tasks'
};
Your original code ended up with tasks being an array of functions that have not been called yet. When you call Promise.all(tasks) it ended immediately with undefined for each entry in the array because there were no Promise objects to wait on.
As for error handling, the Promise.all will fail with the first rejection or exception that happens. If you want to handle each task's individual error case, then you should do that in the Promise created in registerTask since doing it during the Promise.all is too late. It's hard to give an example since I'm not sure what kind of error handling would be appropriate for tasks, but perhaps something like this:
const registerTask = (name, task) => {
const asyncFunc = async () => {
return { [name]: await task() };
};
const promise = asyncFunc()
.catch(err => {
return handleError(name, err);
});
tasks.push( promise );
};
function handleError(name, err) {
console.log(`Task ${name} had error ${err}`);
// don't re-throw the error if you want all tasks to complete
// return some error object so you can see which tasks failed after they're done
return {error: err, name: name};
}
Promise.all expects an array of promises and you are passing an array of functions that returns a promise. So change your promise Promise.all to receive the promise of each function:
// Some dummy tasks:
const taskA = () => Promise.resolve(500);
const taskB = () => {
return new Promise(resolve => resolve(300));
};
// Push them into an array:
const tasks = [];
const registerTask = (name, task) => {
tasks.push( async () => {
return { [name]: await task() };
});
};
// trying to get the results
const runTasks = () => {
const result = Promise.all(tasks.map(task => task())); // Execute the tasks to receive the promises
return result;
}
// usage
registerTask('taskA', taskA);
registerTask('taskB', taskB);
runTasks().then((res) => {console.log(res)})
I have a service to get a list from server. But in this list I need to call another service to return the logo img, the service return ok, but my list remains empty. What i'm did wrong ?
I tried to use async/await in both services
I tried to use a separate function to get the logos later, but my html don't change.
async getOpportunitiesByPage(_searchQueryAdvanced: any = 'active:true') {
this.listaOportunidades = await this._opportunities
.listaOportunidades(this.pageSize, this.currentPage, _searchQueryAdvanced)
.toPromise()
.then(result => {
this.totalSize = result['totalElements'];
return result['content'].map(async (opportunities: any) => {
opportunities.logoDesktopUrl = await this.getBrand(opportunities['brandsUuid']);
console.log(opportunities.logoDesktopUrl);
return { opportunities };
});
});
this.getTasks(this.totalSize);
}
No errors, just my html don't change.
in my
console.log(opportunities.logoDesktopUrl);
return undefined
but in the end return filled.
info:
Angular 7
server amazon aws.
await is used to wait for promise.
You should return promise from getBrand if you want to wait for it in getOpportunitiesByPage.
Change the getBrand function as following.
getBrand(brandsUuid): Observable<string> {
this.brandService.getById(brandsUuid).pipe(map(res => {
console.log(res.logoDesktopUrl); return res.logoDesktopUrl;
}))
}
Change opportunities.logoDesktopUrl = await this.getBrand(opportunities['brandsUuid']); to opportunities.logoDesktopUrl = await this.getBrand(opportunities['brandsUuid']).toPromise();
Please make sure you imported map from rxjs/operators.
At first,when you await, you should not use then.
At second, async/await runs only with Promises.
async getOpportunitiesByPage(_searchQueryAdvanced: any = 'active:true') {
const result = await this._opportunities
.listaOportunidades(this.pageSize, this.currentPage, _searchQueryAdvanced)
.toPromise();
this.totalSize = result['totalElements'];
this.listaOportunidades = result['content'].map(async (opportunities: any) => {
opportunities.logoDesktopUrl = await this.getBrand(opportunities['brandsUuid']);
console.log(opportunities.logoDesktopUrl);
return opportunities;
});
this.getTasks(this.totalSize);
}
getBrand(brandsUuid) {
return new Promise((resolve, reject) => {
this.brandService.getById(brandsUuid).subscribe(res => {
console.log(res.logoDesktopUrl);
return resolve(res.logoDesktopUrl);
}, err => {
return reject(err);
});
});
}
But, because rxjs is a used in Angular, you should use it instead of async/await :
getOpportunitiesByPage: void(_searchQueryAdanced: any = 'active:true') {
this._opportunities.listaOportunidades(this.pageSize, this.currentPage, _searchQueryAdvanced).pipe(
tap(result => {
// we do that here because the original result will be "lost" after the next 'flatMap' operation
this.totalSize = result['totalElements'];
}),
// first, we create an array of observables then flatten it with flatMap
flatMap(result => result['content'].map(opportunities => this.getBrand(opportunities['brandsUuid']).pipe(
// merge logoDesktopUrl into opportunities object
map(logoDesktopUrl => ({...opportunities, ...{logoDesktopUrl}}))
)
),
// then we make each observable of flattened array complete
mergeAll(),
// then we wait for each observable to complete and push each result in an array
toArray()
).subscribe(
opportunitiesWithLogoUrl => {
this.listaOportunidades = opportunitiesWithLogoUrl;
this.getTasks(this.totalSize);
}, err => console.log(err)
);
}
getBrand(brandsUuid): Observable<string> {
return this.brandService.getById(brandsUuid).pipe(
map(res => res.logoDesktopUrl)
);
}
Here is a working example on stackblittz
There might be a simpler way to do it but it runs :-)