I'm trying to understand why the following two code blocks yield different results.
Code Block one works as expected and returns an array of the providers looked up from the database. On the other hand, Code Block two returns an array of functions. I feel like I'm missing something simple here in my understanding of Promise.all() and async / await.
The differences in the code blocks are:
Block 1: Array of promise functions is created and then wrapped in async functions using the map operator.
Block 2: Array of promises functions are created as async functions. Therefore, the map operator isn't called.
In case you aren't familiar with the Sequelize library, the findOne() method that gets called returns a promise.
Also worth mentioning, I know that I could use a single find query with and "name in" where clause to get the same results without creating an array of promises for multiple select queries. I'm doing this simply as a learning exercise on async / await and Promise.all().
CODE BLOCK 1: Using map() inside Promise.all()
private async createProfilePromises(profiles){
let profileProviderFindPromises = [];
//Build the Profile Providers Promises Array.
profiles.forEach(profile => {
profileProviderFindPromises.push(
() => {
return BaseRoute.db.models.ProfileProvider.findOne({
where: {
name: {[BaseRoute.Op.eq]: profile.profileProvider}
}
})}
);
});
//Map and Execute the Promises
let providers = await Promise.all(profileProviderFindPromises.map(async (myPromise) =>{
try{
return await myPromise();
}catch(err){
return err.toString();
}
}));
//Log the Results
console.log(providers);
}
CODE BLOCK 2: Adding the async function without the use of map()
private async createProfilePromises(profiles){
let profileProviderFindPromises = [];
//Build the Profile Providers Promises Array.
profiles.forEach(profile => {
profileProviderFindPromises.push(
async () => {
try{
return await BaseRoute.db.models.ProfileProvider.findOne({
where: {
name: {[BaseRoute.Op.eq]: profile.profileProvider}
}
});
}catch(e){
return e.toString();
}
}
);
});
//Execute the Promises
let providers = await Promise.all(profileProviderFindPromises);
//Log the Results
console.log(providers);
}
Your code basically boils down to:
const array = [1, 2, 3];
function fn() { return 1; }
array.map(fn); // [1, 1, 1]
array.push(fn);
console.log(array); // [1, 2, 3, fn]
You push a function (whether that is async or not does not matter), instead you want to push the result of calling the function:
array.push(fn());
or in your case:
array.push((async () => { /*...*/ })());
How I'd write your code:
return Promise.all(profiles.map(async profile => {
try{
return await BaseRoute.db.models.ProfileProvider.findOne({
where: {
name: { [BaseRoute.Op.eq]: profile.profileProvider }
}
});
} catch(e) {
// seriously: does that make sense? :
return e.toString();
}
}));
Related
I am having this code:
signers.map(async (signer, tx) => {
const address = await signer.getAddress();
console.log(address);
return { account: address, amount: 1 }
})
console.log("awesome");
The problem is that awesome gets printed first. How do I make sure that until all the promises are resolved inside map, it doesn't come to awesome. The cleanest way would be much appreciated.
All async functions return a promise that resolves when the body of the function has completed or rejects when an exception occurs.
And, .map() is not async-aware at all. It doesn't pause the loop waiting for the promise. So, you are running all your asynchronous operations in-flight at the same time and signers.map() returns an array of promises. If you want to know when they are all done and get the results of all those promises, you need to use Promise.all() on that array of promises.
let promises = signers.map(async (signer, tx) => {
const address = await signer.getAddress();
console.log(address);
return { account: address, amount: 1 }
});
Promise.all(promises).then(results => {
console.log("awesome");
console.log(results);
}).catch(err => {
console.log(err);
});
If, what you really want to do is to run your asynchronous operations serially, one after the other, then use a regular for loop in a containing async function because a for loop is async-aware and will pause the loop waiting for the await.
let addresses = [];
for (let signer of signers) {
const address = await signer.getAddress();
console.log(address);
addresses.push({ account: address, amount: 1 });
}
console.log(addresses);
Note that there is nothing stopping you from awaiting Promise.all since it's just a promise of results;
const promises = signers.map(async (signer, tx) => {
const address = await signer.getAddress();
console.log(address);
return { account: address, amount: 1 }
});
const results = await Promise.all(promises);
console.log("awesome");
I am new to React and Promises.
My purpose is - Collect array of objects from several Search Services and then transfer whole array to the State.
PNP.SearchResults is a Promise
In code below this.setState is performing earlier then array is ready.
How to fix it?
private getSearchResults() {
const allSearchResults = []
this.state.resultSources.forEach(rSource =>{
this.props.searchService.search(this.props.AdditionalQuery, this.state.activePage, this.props.PageSize, rSource.sourceGuid).then((results: PNP.SearchResults) => {
allSearchResults.push({Results: results, sourceGuid: rSource.sourceGuid});
console.log("push");
}).catch(() => {});
})
this.setState({PrimaryResults2: allSearchResults} as any);
console.log("state updated");
}
Now console.log("state updated") fires earlier then console.log("push").
But I need vice versa
Because of this.props.searchService.search is async function.
You should await result to make sure data return.
private async getSearchResults() {
const allSearchResults = []
for (const rSource of this.state.resultSources) {
await this.props.searchService.search(this.props.AdditionalQuery, this.state.activePage, this.props.PageSize, rSource.sourceGuid).then((results: PNP.SearchResults) => {
allSearchResults.push({Results: results, sourceGuid: rSource.sourceGuid});
console.log("push");
}).catch(() => {});
}
this.setState({PrimaryResults2: allSearchResults} as any);
console.log("state updated");
}
If i understand correctly, you need to first push the search results into allSearchResults array and then setState. Why not use async await and a basic for loop instead of .then. When you use .then, only the code in the .then callback will execute after the promise is resolved but the other code outside it like this.setState, console.log won't wait till you push all the search results.
async func() {
const allSearchResults = [];
for(let i=0; i<this.state.resultSources.length; i+=1){
const item = await this.props.searchService.search(this.props.AdditionalQuery, this.state.activePage, this.props.PageSize, this.state.resultSources[i].sourceGuid)
allSearchResults.push({Results: item, sourceGuid: this.state.resultSources[i].sourceGuid})
}
this.setState({PrimaryResults2: allSearchResults} as any, () => {console.log("state updated")})
}
I'm calling the service sending an array of ids as a parameter. In the function I need to do a foreach of this ids and make an api call for each one to get the full info of this element and after that do something with that info.
so how can I stop the execution of foreach until the end of the API call it has inside?
The function:
addCandidate(ids: any, candidate: string) {
ids.forEach(elem => {
this.getSearch(elem).subscribe(res => {
let currentCandidates = res.candidates;
if(currentCandidates) {
if(!currentCandidates.includes(candidate)){
currentCandidates.push(candidate);
this.itemDoc.update({candidates: currentCandidates});
}
}else{
this.itemDoc.update({candidates: [candidate]});
}
})
});
}
Thanks!!
Here is an Example how to use promise and map.
let doSomthingToWaitFor = (ms) => {
return new Promise(resolve => setTimeout(() => resolve('r-' + ms), ms))
}
(async (ids) => {
await Promise.all(ids.map(async id => {
let res = await doSomthingToWaitFor(id);
console.log("my res: %s from id: %s", res, id);
}));
console.log("Successfully waited");
})([1000, 2000, 3000, 4000]);
You can implement your request in a function like doSomthingToWaitFor and handle the result afterwards.
It sounds like you want to map an array to a set of promises, and then wait for all that concurrent work to finish before doing something else.
I think you will find map, promise and async/await very helpful here, e.g.
const ids = [1, 2];
const work = await Promise.all(
ids.map(ajaxCall)
);
// 'await' means that this won't be called until the Promise.all is finished
printToScreen(work);
Using Express, I am using .map to create an array of API calls, whose results I want to combine into one response object. Since every call uses different query parameters, I would like to use the query parameters as the response object's keys.
I create the GET requests using axios, which returns a promise, and then I use axios.all to wait for all of the promises to resolve.
The problem is that after the promises resolve, I no longer have access to the variables used to create them. How can I attach those variables to the promises for later reference?
Here is the API:
router.get('/api', (req, res) => {
const number = req.query.number;
res.json({ content: "Result for " + number });
});
Here is where I am trying to combine the results:
router.get('/array', async (req, res) => {
res.locals.payload = {};
const arr = [1, 2, 3, 4, 5];
const promises = arr.map(number => {
return axiosInstance.get('/api', { params: { number: number } })
});
const results = await axios.all(promises);
results.map(r => {
// number should match original, but I no longer have
// access to the original variable
number = 1;
res.locals.payload[number] = r.data;
});
res.json(res.locals.payload);
});
Result of GET on /array:
{
"1": {
"content": "Result for 5"
}
}
What can I do when creating the Promise objects to preserve the keys?
If the result were going to be an array with 0-based indexes instead of an object with properties "1", "2", etc., we could solve it with Promise.all (or axios.all if it offers the same guarantee Promise.all does that the array it gives you will be in the order of the promises you give it, regardless of the order in which they resolve). But I'm guessing your "number" is really a placeholder for something more interesting. :-)
You can do this by making use of the fact that a promise is a pipeline where the various handlers transform the contents along the way.
In this case, you can transform the result of the get call into an object with the number and result:
const promises = arr.map(number => {
return axiosInstance.get('/api', { params: { number } })
.then(result => ({result, number}));
});
Now, results in your all.then callback receives an array of objects with result and number properties. You could use destructuring parameters to receive them in your results.map callback if you like:
// vvvvvvvvvvvvvvvv---------------- destructuring parameters
results.map(({result, number}) => {
res.locals.payload[number] = result.data;
// ^^^^^^----^^^^^^-------- using them
});
Live Example:
// Fake axiosInstance
const axiosInstance = {
get(url, options) {
return new Promise(resolve => {
setTimeout(() => {
resolve({data: `Data for #${options.params.number}`});
}, 300 + Math.floor(Math.random() * 500));
});
}
};
// Fake axios.all:
const axios = {
all: Promise.all.bind(Promise)
};
// Fake router.get callback:
async function routerGet() {
const payload = {}; // stand-in for res.locals.payload
const arr = [1, 2, 3, 4, 5];
const promises = arr.map(
number => axiosInstance.get('/api', { params: { number } })
.then(result => ({result, number}))
);
const results = await axios.all(promises);
results.map(({result, number}) => {
/*res.locals.*/payload[number] = result.data;
});
console.log(/*res.locals.*/payload);
}
// Test it
routerGet().catch(e => { console.error(e); });
Note I've used ES2015+ property shorthand above, since you're using arrow functions and such. The object initializer { number } is exactly the same as { number: number }.
Side note 2: You can use a concise arrow function for the map callback if you like:
const promises = arr.map(
number => axiosInstance.get('/api', { params: { number } })
.then(result => ({result, number}))
);
I have found two handy modules called run-parallel and run-series to run arrays functions and return arrays of results.
Looking at the little number of contributors and stars in the Github projects, I wonder if there is a canonical way of doing these tasks instead of installing these modules?
Maybe there is a native way in Node or in ES6 to do this that I am missing?
Examples
First see some examples - scroll below for an explanation.
Callbacks:
Example with async and functions that take Node-style callbacks:
async.parallel([
(cb) => {
setTimeout(() => {
cb(null, 'one');
}, 200);
},
(cb) => {
setTimeout(() => {
cb(null, 'two');
}, 100);
},
],
(err, results) => {
if (err) {
// there was an error:
console.log('Error:', err);
return;
}
// we have ['one', 'two'] in results:
console.log('Results:', JSON.stringify(results));
});
Promises:
Example using functions that return promises - with Bluebird's delay() function:
const { delay } = require('bluebird');
Promise.all([
delay(200, 'one'),
delay(100, 'two'),
]).then((results) => {
// we have ['one', 'two'] in results:
console.log('Results:', JSON.stringify(results));
}).catch((err) => {
// there was an error:
console.log('Error:', err);
});
ES2017 async/await:
Using async/await:
const { delay } = require('bluebird');
try {
const results = await Promise.all([
delay(200, 'one'),
delay(100, 'two'),
]);
// we have ['one', 'two'] in results:
console.log('Results:', JSON.stringify(results));
} catch (err) {
// there was an error:
console.log('Error:', err);
}
I'm using JSON.stringify() to make it explicit what is the format of the data in results.
Note that even though the first value comes last, the original order is still preserved.
The last example must be run inside of a function declared with an async keyword, or wrapped in (async () => { ... })() like this:
(async () => {
try {
const results = await Promise.all([
delay(200, 'one'),
delay(100, 'two'),
]);
// we have ['one', 'two'] in results:
console.log('Results:', JSON.stringify(results));
} catch (err) {
// there was an error:
console.log('Error:', err);
}
})();
Generators and coroutines:
With no support for async/await you can use some generator-based coroutines, like those that comes from Bluebird:
const { delay, coroutine } = require('bluebird');
coroutine(function* () {
try {
const results = yield Promise.all([
delay(200, 'one'),
delay(100, 'two'),
]);
// we have ['one', 'two'] in results:
console.log('Results:', JSON.stringify(results));
} catch (err) {
// there was an error:
console.log('Error:', err);
}
})();
Explanation
There are many ways to do that and it all depends on the kind of functions that you want to run.
If you want to run a traditional Node-style functions that take error-first callbacks as their last arguments then the most popular way to run those in parallel or in series is the async module on npm:
https://www.npmjs.com/package/async
There is no built-in support for things like that in ES6 because those error-first callbacks are really a Node thing, not very popular in JavaScript outside of Node.
The ES6/ES7/ES8 goes in the direction of functions that return promises (instead of functions that take callbacks) and there is a new async/await syntax to make them look kind-of-like synchronous, with try/catch working for error handling.
So, the most popular way in Node to combine functions taking callbacks is async module:
https://www.npmjs.com/package/async
To work with promises a popular modules is Bluebird:
https://www.npmjs.com/package/bluebird
For more advanced tasks there is Task.js:
https://github.com/mozilla/task.js
See those answers for more info:
try/catch blocks with async/await
node.js ~ constructing chained sequence of Promise resolves
How to run Generator Functions in Parallel?
node.js ~ constructing chained sequence of Promise resolves
Using async/await + Bluebird to promisifyAll
jQuery: Return data after ajax call success
Remember that functions can just be passed around like any other object in JavaScript, so it's actually pretty straightforward to do things like this.
For example, to run things in series you could just use map to generate an array of results by executing each of your functions - like this...
let functions = [
function() { return 'abc'; },
function() { return 123; },
function() { return { key: 'value' }; }
];
let results = functions.map(fn => fn());
Obviously, what you get from using these libraries is nice - hopefully well tested - handling for when things go wrong etc. but it's perfectly possible to do it without them.
You can use Array.prototype.map() to loop through all functions in your array sequentially, using an anonymous function that executes each function in the array as a parameter.
Using Function.prototype.apply() instead of directly calling the function allows you to pass an array to each function with its parameters.
Example
var functions = [
function(x){return 3 * x},
function(x){return 4 * x},
function(x){return 8 * x},
function(x){return 10 * x},
function(){return Array.prototype.map.call(arguments, function(name){
return "Hello, " + name
})}
];
var results = functions.map(function(value, index) {
return value.apply(value, this[index]);
}, [[5], [1], [8], [8], ["Dolly", "major Tom"]]);
document.body.innerHTML = '<pre>' + JSON.stringify(results, null, '\t') + '</pre>';
See also this Fiddle.
The structure depends on what you want to implement.
:D
functions = {};
async function getArticle(id = 0) {
// const result = await articles.get(id); get result of a db.
const result = { id: 10, name: "apple" }
if (result) return result;
return false;
}
async function validatePermission(id = 0) {
// const result = await users.get(id); get result of a db.
const result = { id: 25 }
if (result) return result;
return false;
}
functions.getArticle = async params => {
const { userId, articleId } = params;
const [validUser, article] = await Promise.all([
validatePermission(userId),
getArticle(articleId),
]);
if (validUser === false || article === false) {
return { status: 400, message: 'Error', data: [] }
}
return { status: 200, message: 'Success', data: article };
}
const params = { userId: 25, articleId: 10 };
functions.getArticle(params)
.then(result => {
console.log(result);
});