Promise.all with dependency of first promise response on second promise - javascript

I am trying to use Promise.all of which, second promise is dependent on first promise response.
Here is my code
let user = await userService.getByKey({ _id: params.userId });
let room = await matchService.findUserInRoom(user._id);
let match = await matchService.findMatch(params.userId);
But what now I was trying is to do it using Promise.all. Something like
let [user, room, match ] = await Promise.all([firstPromise, secondPromise, thirdPromise])
And I just don't know how to do it this way, tried a lot to find some reference but unable to. As because my second promise is dependent on the response of first promise just stuck how to pass this way

You can't call the first and second asynchronous API calls concurrently, but third one is independent so you can do it like the following:
async function getRoom(params) {
const user = await userService.getByKey({ _id: params.userId });
const room = await matchService.findUserInRoom(user._id);
return [user, room];
}
const [[user, room], match] = await Promise.all([
getRoom(params),
matchService.findMatch(params.userId)
]);

Related

Why objects key still has unknow promise , though I have used await for mapping

I'm new to javascript. as I know using array.map over async function returns array of promises.
and I can use await Promise.all to reolve all promise and it returns me data.
I want to understand how can I use asyn function inside array of object's key.
As I know using async function it never block as execution for unimportant line, ( here other execution is not dependent on url , so I'm trying to make it asynchronous)
async function showPost() {
const posts = [
{ title: 'a', url: ['q', 'o'] },
{ title: 'b', url: ['t', 'y'] },
];
const formattedPost = await formatPosts(posts);
console.log(formattedPost);
}
const formatPosts = async (posts) => {
const formattedPosts = await Promise.all( // NOTE: await promise.all on map
posts.map(async (post) => {
post.url = addUrl(post.url); //NOTE: here if I don' add await here post.url is Promise<Unknown>
return post;
})
);
return formattedPosts;
};
const addUrl = async (d) => {
const mm = await Promise.all(d.map((item) => item + '-dummyUrl'));
return mm;
};
showPost();
**CODE 1 without await , but inside await Promise.all **
CODE 2 with AWAIT on addURL call I get output
Why is it not resolving though it is inside await promise.all
thanks for help in advance
When you call Promise.all(), you're resolving the promises returned by the map() function. When you don't add await to addURL(), you're replacing that value in the object with a promise object, which isn't resolved by Promise.all().
Promises are still promises, even after they resolve.
On top of this, Array.prototype.map() isn't an async function, so addURL() doesn't need to be async, which makes adding await pointless.
I know using array.map over async function returns array of promises, and I can use await Promise.all to resolve all promise and it returns me data.
This will just wait for the promises that are elements of the array that was passed to Promise.all, not "all promises" anywhere.
Notice that without the await, you don't need to make the map callback an async function, and you don't need to wait for the promises in the mapped array then; your current code is exactly equivalent to
const formatPosts = async (posts) => {
const formattedPosts = posts.map((post) => {
post.url = addUrl(post.url);
return post;
});
return formattedPosts;
};
Why objects key still has unknown promise
Because you assigned a promise object to the object property. Never did you instruct anything to wait for that promise. This is what would be fixed by doing
post.url = await addUrl(post.url);

Wait for a query in MongoDB

I have this async method written in Typescript to query, with the nodejs driver, a MongoDB; the compiler indicates that the "await" before "myConnectedClient" has no effect on the type of this expression; I'm confused: is the call to the aggregate() asynchronous? So, I have to wait, or not?Thanks.
async findQuery<T>(
collection: string,
findParams: Query<T>,
sort: Sort<T>,
myConnectedClient: MongoClient
) {
const firstResult = await myConnectedClient // the compiler indicates await is useless
.db("ZZZ_TEST_ALL")
.collection("my_collection_01")
.aggregate<string>([{ $project: { _id: 0, name: 1 } }]);
firstResult.forEach((field) => {
console.log(`Field: ${field}`);
});
}
UPDATE: I have to add .toArray() after the .aggregate() call; but why? Can anybody explain me the mechanism? aggregate() has not callback and does not return a promise? Are there alternatives to .toArray()? Thanks.
// now await it's ok
const firstResult = await myConnectedClient
.db("ZZZ_TEST_ALL")
.collection("my_collection_01")
.aggregate<string>([{ $project: { _id: 0, name: 1 } }]).toArray();
Aggregate is sync and returns an AggregationCursor.
The cursor has number of async methods to retrieve actual data: toArray, forEach, or simple iterator
In the first snippet firstResult is the cursor, so there is no need to await.
You use firstResult.forEach to log the results. It does return the promise but you ignore it, which will bite you - the promise returned by findQuery will be resolved immediately while forEach will iterate results in parallel. To keep the promise chain you should do
const firstResult = myConnectedClient.......;
return firstResult.forEach(......);
so the promise returned from the findQuery will be resolved only when forEach is resolved, e.g. you finish iterate the results.
In the second "update" snippet, the firstResult is the data, so you need the await to get it from toArray(). The equivalent with explicit cursor would be:
const cursor = myConnectedClient.......;
const firstResult = await cursor.toArray();

JS: return promise from an async function without resolving the promise?

I'm pretty sure this isn't possible, but asking just in case.
Let's say I have a bunch of functions that add to a query builder:
let query = User.query();
query = filterName(query, name);
query = filterLocation(query, location);
const users = await query;
This is fine. However, if I need one of these functions to be async (e.g. to fetch some data), I can't await the function because it'll resolve the whole query.
async function filterLocation(query, location) {
const data = await ...;
return query.where(...);
}
...
query = await filterLocation(query, location); // `query` is now resolved to a list of users, but I want it to remain a promise
Is there a way to make JS not resolve the promise returned by filterLocation? Do I have to wrap it in an object?
await can only be used inside an async function.
Option 1: invoke an async anonymous function
...
(async function(){
query = await filterLocation(query, location);
})();
Option 2: use promise API (then)
...
filterLocation(query, location)
.then(function (query){
...
});

Async/await: do async nested functions automatically need to be awaited a level above?

I have a couple questions regarding how async/await flows across levels. Some example code:
class Auth
async validateJwt() { // A
const result = await fetch(...) // #1
return await result.json(); // #1.5
}
async createJwtOrValidateExisting() { // B
const token = await validateJwt(); // #2
return token.data;
}
}
const auth = new Auth();
// React.js top level component
const App = props => {
useEffect(() => { // C
const tokenData = auth.createJwtOrValidateExisting(); // #3
}
}
Do I need to await at both steps 2 and 3?
If so, I think I need an async keyword on line C as well.
I know that await pauses execution, which is needed at the fetch() and .json() steps. In the first method this is intuitive.
But then when you await at step #2, I believe this also requires the async keyword on the higher level function, which then means a promise must be returned from it. Which seems inappropriate because the .json() step was already handled/resolved on step 1.5.
If this interpretation is not correct and I do need async/await on steps B / #2, that implies I must also turn the useEffect callback into an async function as well.
Any explanation on the proper usage of these keywords above would help a lot, thank you.
Do I need to await at both steps 2 and 3?
For #2, yes. validateJwt is an async function. If you call it without an await, you get a promise. If you call it with an await, you get the resolved value of that promise.
For #3, useEffect is NOT an async function, so you must use then to handle the promise returned by createJwtOrValidateExisting. Alternatively, you can make useEffect an async function, and use await on createJwtOrValidateExisting.
Also, in #1.5, the await on return await result.json() is not necessary. response.json() returns a promise, which you can return immediately from an async function. See https://eslint.org/docs/rules/no-return-await
This should be:
class Auth
async validateJwt() {
const result = await fetch(...)
return result.json()
}
async createJwtOrValidateExisting() {
const token = await validateJwt()
return token.data
}
}
const auth = new Auth();
const App = props => {
async useEffect(() => {
const tokenData = await auth.createJwtOrValidateExisting()
}
}
The rule of thumb is that an async function is just a function that returns a promise. If you want to extract its value without using then, then make the function async, and use await on that promise.

Catch Promise rejection at a later time [duplicate]

This question already has answers here:
Waiting for more than one concurrent await operation
(4 answers)
Closed 4 years ago.
How do I retrieve the result of a promise at a later time? In a test, I am retrieving an email before sending further requests:
const email = await get_email();
assert.equal(email.subject, 'foobar');
await send_request1();
await send_request2();
How can I send the requests while the slow email retrieval is going on?
At first, I considered awaiting the email later:
// This code is wrong - do not copy!
const email_promise = get_email();
await send_request1();
await send_request2();
const email = await email_promise;
assert.equal(email.subject, 'foobar');
This works if get_email() is successful, but fails if get_email() fails before the corresponding await, with a completely justified UnhandledPromiseRejectionWarning.
Of course, I could use Promise.all, like this:
await Promise.all([
async () => {
const email = await get_email();
assert.equal(email.subject, 'foobar');
},
async () => {
await send_request1();
await send_request2();
},
]);
However, it makes the code much harder to read (it looks more like callback-based programming), especially if later requests actually depend on the email, or there is some nesting going on. Is it possible to store the result/exception of a promise and await it at a later time?
If need be, here is a testcase with mocks that sometimes fail and sometimes work, with random timings. It must never output UnhandledPromiseRejectionWarning.
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const send_request1 = () => wait(300), send_request2 = () => wait(200);
async function get_email() {
await wait(Math.random() * 1000);
if (Math.random() > 0.5) throw new Error('failure');
return {subject: 'foobar'};
}
const assert = require('assert');
async function main() {
// catch possible error
const email_promise = get_email().catch(e => e);
await send_request1();
await send_request2();
// wait for result
const email = await email_promise;
// rethrow eventual error or do whatever you want with it
if(email instanceof Error) {
throw email;
}
assert.equal(email.subject, 'foobar');
};
(async () => {
try {
await main();
} catch(e) {
console.log('main error: ' + e.stack);
}
})();
In case it's guaranteed that promise rejection will be handled later, a promise can be chained with dummy catch to suppress the detection of unhandled rejection:
try {
const email_promise = get_email();
email_promise.catch(() => {}); // a hack
await send_request1();
await send_request2();
const email = await email_promise;
assert.equal(email.subject, 'foobar');
} catch (err) {...}
The problem with this approach is that there are two concurrent routines but the code doesn't express this, this is a workaround for what is usually done with Promise.all. The only reason why this workaround is feasible is that there are only 2 routines, and one of them (get_email) requires to be chained with then/await only once, so a part of it (assert) can be postponed. The problem would be more obvious if there were 3 or more routines, or routines involved multiple then/await.
In case Promise.all introduces unwanted level of lambda nesting, this can be avoided by writing routines as named functions, even if they aren't reused anywhere else:
async function assertEmail() {
const email = await get_email();
assert.equal(email.subject, 'foobar');
}
async function sendRequests() {
await send_request1();
await send_request2();
}
...
try {
await Promise.all([assertEmail(), sendRequests()]);
} catch (err) {...}
This results in clean control flow and verbose but more intelligible and testable code.
So, I want to explain why we behave this way in Node.js:
// Your "incorrect code" from before
const email_promise = get_email(); // we acquire the promise here
await send_request1(); // if this throws - we're left with a mess
await send_request2(); // if this throws - we're left with a mess
const email = await email_promise;
assert.equal(email.subject, 'foobar');
That is, the reason we behave this way is to not deal with the "multiple rejections and possibly no cleanup" scenario. I'm not sure how you ended up with the long code for Promise.all but this:
await Promise.all([
async () => {
const email = await get_email();
assert.equal(email.subject, 'foobar');
},
async () => {
await send_request1();
await send_request2();
},
]);
Can actually be this:
let [email, requestData] = await Promise.all([
get_email(),
send_request1().then(send_request2)
]);
// do whatever with email here
It's probably what I would do.

Categories

Resources