How to resolve nested promises with Promise.all() - javascript

I am trying to write something like this but not able to resolve inner promises.
const traceSamplesRequests = transactionTypes.map(async (transactionType) => {
// This part should be synchronous since we need transactionNames
const transactionNames = await api.getTransactionNames();
return transactionNames.map((transactionName) => api.getTraceSamples());
});
And I want to write with lambda functions with Promise.all not imperative loops.

So, .map() is not promise-aware. It calls the callback and just dutifully collects the return value from the callback and advances to the next iteration of the loop. Since your callback is async with an await in it, that return value is an unresolved promise. So, what you get back from .map() is an array of promises.
So, there is no way to use .map() or any of the array iteration methods without then using something to wait for the promises.
It sounds like you already know this, but with the code you have, you would insert two Promise.all() statements:
const traceSamplesRequests = await Promise.all(transactionTypes.map(async (transactionType) => {
// This part should be synchronous since we need transactionNames
const transactionNames = await api.getTransactionNames();
return Promise.all(transactionNames.map((transactionName) => api.getTraceSamples()));
}));
That would get you an array of values (and the parent function would have to be async). This will run all the asynchronous operation in the loop in parallel (all in flight at the same time).
Your other choices are to use a for or while loop which will suspend loop iteration for each await and will run your asynchronous operations in sequence (second iteration of the loop not running until the first is done).
Or, you can write some sort of helper function that would be a promise-aware version of .map(). You'd probably just either use for or .map() with Promise.all() inside the helper function though.
The Async promise library has multiple such helper functions as choices to use. They are designed to work with either promises or plain callbacks.

I actually made it work like this:
const traceSamplesRequests = await Promise.all(
transactionTypes.map(async (transactionType) => {
const transactionNames = await api.getTransactionNames({ transactionType });
return await Promise.all(transactionNames.map((transactionName) => api.getTraceSamples({ transactionType })));
})
);

Related

Do i need to make this function asynchronous to wait for the filter method?

I have the state array "days" and I have a function that gets the element with an id given by the argumet, i am using the array.filter method to do this. Assuming the array has a lot of data in it, do i need to make the function asynchronous to wait for the filter method to print that data?
const [days, setDays] = useState([]);
const onDayClicked = async (id) => {
const daySelected = await days.filter(el => el._id === id)
console.log(daySelected)
}
filter returns:
A new array with the elements that pass the test. If no elements pass the test, an empty array will be returned.
An array is not a promise.
await is only useful if you have a promise on the RHS.
If you have an expensive, blocking operation to do, then it is simply an expensive operation that will block the event loop. You can't get around that by throwing promise syntax at it.
You could get around it by moving the work out of the main event loop and into a web worker.
If you did that, then you could create a promise that resolves when the web worker sends the results back to the main event loop.
And if you did that, then you could use await so that daySelected would have the resolved value in it.

Is the promise resolved, when I push promise array?

I want to push promise to an array. Then I want to resolve it using Promise.all(). But I'm not sure, is promise work when i push to array?
For example:
const productIds= [1,2,3,4,5,6] // example
const promises = [];
productIds.map(productId => promises.push(ProductDataAccess.updateProductStock(store,productId,incQuery)));
If I don't Promise.all(), will db process occur? Or does the db process occur when I make Promise.all().
const productIds= [1,2,3,4,5,6] // example
const promises = [];
productIds.map(productId => promises.push(ProductDataAccess.updateProductStock(store,productId,incQuery)));
Pormise.all(promises);
If I don't Promise.all(), will db process occur?
It depends. The general answer is yes. But be aware that this is not always true.
Normally, functions that return a Promise schedules an asynchronous process when you call them. So the asynchronous process will happen regardless of you waiting for them.
However, there are some functions that don't really return a promise. They return a promise-like object. And sometimes (not always) they don't start the asynchronous process unless you call .then(). And database libraries that use fluent/chainable functions do this. This is a common design pattern in database libraries:
// knex example:
let x = knex('my_table').select(['id','some_info']); // will not trigger db query
console.log(x); // knex object - not a Promise!
x = x.where('id', 0); // still no db query
console.log(x); // still not a Promise!
x = x.then(result => console.log(result)); // TRIGGERS DB QUERY!
console.log(x); // Yay! A Promise!
Lots of database libraries do this in order to implement a fluent/chaining style API that is transparent to the user. They detect the end of query construction by the .then() function being called.
Now, I don't know what database library you are using but if you are affected by this then to trigger the database query process you will need to call then either directly or indirectly:
call .then() yourself
pass the object to Promise.all() which internally calls .then()
await the result in an async function which internally calls .then()
Yes, promises run when they are created, not when they are awaited upon (be it with Promise.all() or otherwise).
You will start the execution of the promises from the moment each promise is created (regardless of the usage of Promise.all), but not wait for them.
If this block of code is in an async function, you can await the promises in this way (and also, you can simply use map, without push, to build the array of promises you need):
const productIds = [1,2,3,4,5,6];
const promises = productIds.map((productId) => ProductDataAccess.updateProductStock(store,productId,incQuery));
await Promise.all(promises);

Construct object from multiple promises

I want to construct an object from multiple promise results. The only way to do this is something like this:
const allPromises = await Promise.all(asyncResult1, asyncResult2);
allPromises.then([result1, result2] => {
return {result1, result2};
})
A nicer way (especially when more data is being constructed) would be this, with the major drawback of the async requests happening one after the other.
const result = {
result1: await asyncResult1(),
result2: await asyncResult2(),
}
Is there a better way to construct my object with async data I am waiting for?
See my comment, it's a little unclear what asyncResult and asyncResult2 are, because your first code block uses them as though they were variables containing promises (presumably for things already in progress), but your second code block calls them as functions and uses the result (presumably a promise).
If they're variables containing promises
...for processes already underway, then there's no problem with awaiting them individually, since they're already underway:
const result = {
result1: await asyncResult1, // Note these aren't function calls
result2: await asyncResult2,
};
That won't make the second wait on the first, because they're both presumably already underway before this part of the code is reached.
If they're functions
...that you need to call, then yes, to call them both and use the results Promise.all is a good choice. Here's a tweak:
const result = await Promise.all([asyncResult1(), asyncResult2()])
.then(([result1, result2]) => ({result1, result2}));
One of the rare places where using then syntax in an async function isn't necessarily the wrong choice. :-) Alternately, just save the promises to variables and then use the first option above:
const ar1 = asyncResult1();
const ar2 = asyncResult2();
const result = {
result1: await ar1,
result2: await ar2,
};

Does Promise.all() execute an array of functions or do they execute when you put them into the array?

Since await does not work inside Array.map or Array.reduce, can you do something like the following or would this be considered misuse of Promise.all? Normally, neo4j.session() would be awaited.
// inside a function
const QUERY = 'MATCH (n) RETURN n'
const argsArray = [{ sample: 'sadf' }, { sample: 'sadf' }, { sample: 'sadf' }]
const runQueries = argsArray.map(obj => neo4j.session.run(QUERY, obj.sample))
await Promise.all(runQueries)
.then(results => results.forEach(result => console.log(result)))
Does Promise.all() execute an array of functions?
No its an array of promises
or do they execute when you put them into the array?
Exactly, When you build the Promises they're executed.
would this be considered misuse of Promise.all?
No this is totally fine, its actually the point of Promise.all.
However you might do (one after another instead of parallel execution) :
(async function(){
for(const obj of argsArray)
console.log( await neo4j.session.run(QUERY, obj.sample));
})()
async.await is supposed to be syntactic sugar for sequential promise chains. Considering that database queries are supposed to run concurrently, it's perfectly fine to use await Promise.all(...) in such cases.
Promise.all accepts an array of promises (more specifically, an iterable), and promises start to execute at the moment when they are created. It may be the moment prior to Promise.all call:
const promises = [Promise.resolve(1), Promise.resolve(2)];
// promises have been created at this point
Promise.all(promises).then(...)
Or it may be not. If an iterable is not an array but a generator, promises will be lazily created during Promise.all call:
const promiseGen = (function* () {
yield Promise.resolve(1);
yield Promise.resolve(2);
})();
// promises have not been created yet at this point
Promise.all(promiseGen).then(...)
// promises have been created

When using the await?

I'm using sequelize with typescript. I know that the code is asynchronous, and
Here, I am using promise, and the code works..
I would want to know when I must to use await keyword ?
const promises = []
let tabIdDoc = requestedListIdDoc.toString().split(",")
for (let thisIdDoc of tabIdDoc) {
promises.push(sequelize.models['Document'].findById(thisIdDoc))
}
q.all(promises).then((resultReq) => {
const lstDocs = []
for (let MyDoc of resultReq) {
if (MyDoc.matriculeDoc != "") {
lstDocs.push(sequelize.models['FolderDocContent'].findOrCreate({
where: {
}
}))
}
}
q.all(lstDocs).then(() => {
return response.status(201)
})
}
Is await keyword necessary here ?
You don't ever have to use await as other programming using .then() can always get the job done, but there are numerous times when using await makes your code simpler. This is particularly true when you are trying to sequence a number of asynchronous operations and even more so when you need to use previous results in more than one operation that follows.
Example #1: Serializing operations in a for loop
Suppose you want to save a bunch of items to your database, but for various reasons, you need to save them one by one and you need to save them in order (in other words, you need to sequence them):
async function saveItems(shoppingList) {
for (let item of shoppingList) {
// for loop will pause until this promise resolves
await db.save(item);
}
}
saveItems(myList).then(() => {
// all done
}).catch(err => {
// error here
});
Without using await, you'd have to use a significantly more verbose design pattern using .reduce() or perhaps a recursive function that you call on the completion of the prior operation. This is a lot simpler way to sequence iteration of a loop.
Example #2: Sequencing different operations in a function
Suppose, you need to contact three different outside services. You need to get some data from one, then use that data when you make a second API call, then use both of those pieces of data in a third API call:
const rp = require('request-promise');
async function getPrice(productName) {
// look up productID
const productID = await rp(`http://service1.com/api/getID?name=${productName}`);
// use productID to lookup price
const productPrice = await rp(`http://service1.com/api/getPrice?id=${productID}`);
// put both productID and price into the shopping cart
return rp({uri: 'http://service1.com/api/addToCart', body: {name: productName, id: productID}, json: true);
}
getPrice("Nikon_d750").then(total => {
// all done here, total is new cart total
}).catch(err => {
// error here
});
Both of these examples would require more code in order to properly sequence the asynchronous operations and you'd have to nest logic following inside a .then() handler. Using await instructs the JS interpreter to do that nesting for you automatically.
Some other examples here on MDN.
Several rules to keep in mind with await:
You can only use it inside a function prefixed with the async keyword as in my examples above.
All async functions return a promise. If you have an explicit return value in the function as in return x, then that value becomes the resolved value of the promise when all the asynchronous operations are done. Otherwise, it just returns a promise that has an undefined resolved value. So, to use a result from an async function or to know when it's done, you either have to await the result of that function (inside another async function) or you have to use .then() on it.
await only suspends execution within the containing function. It is as if the rest of the code in the function is placed inside an invisible .then() handler and the function will still return its promise immediately when it hits the first await. It does not block the event loop or block other execution outside the async function. This confuses many people when they first encounter await.
If the promise that you await rejects, then it throws an exception within your function. That exception can be caught with try/catch. If it is not caught, then the async function automatically catches it and rejects the promise that the function returns where the thrown value is the reject reason. It is easy when first using await to completely forget about the error cases when promises you are awaiting can reject.
You never necessarily must use await. You however should use await in every place where you'd otherwise have then with a callback, to simplify your code.
In your example:
const promises = requestedListIdDoc.toString().split(",").map(thisIdDoc =>
sequelize.models['Document'].findById(thisIdDoc)
);
const resultReq = await q.all(promises);
const lstDocs = resultReq.filter(myDoc => MyDoc.matriculeDoc != "").map(myDoc =>
sequelize.models['FolderDocContent'].findOrCreate({
where: {}
})
);
await q.all(lstDocs);
return response.status(201)

Categories

Resources