Wait for a query in MongoDB - javascript

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();

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);

Using async/await with callback functions

I'm trying to build a service layer on top of mongo. I have a User object which has an array of referenced Achievements.
After I've authenticated the user by JWT or some other means, I enter the service layer and query the relationship as follows.
findForUser(userId: string | Types.ObjectId): Promise<Achievement[]> {
return new Promise((resolve) => {
UserSchema.findOne({ _id: userId },
async (err: any, user: User) => {
const AchievementMap: Achievement[] = [];
if (err) throw new Error(err);
user.achievements?.forEach((a) => {
// #ts-ignore
AchievementMap.push(a);
});
resolve(AchievementMap);
});
});
}
What is the async/await approach to returning the result of the callback method passed into UserSchema.findOne?
findOne returns an awaitable object - so you don't need to pass a callback to it. Don't try mixing callbacks with async/await. The only way to return the value from the callback, as a result of the constructed promise is by using resolve (only available in the promise executor).
Instead, make it all async - functionally similar but far cleaner.
async findForUser(userId: string | Types.ObjectId): Promise<Achievement[]> {
const user = await UserSchema.findOne({ _id: userId });
const AchievementMap: Achievement[] = [];
user.achievements?.forEach(a => {
AchievementMap.push(a);
});
return AchievementMap;
}
What is the async/await approach to returning the result of the callback
The async and await keywords are tools to manage promises. They aren't tools to manage callbacks.
The entire point of the code you have in the question is to wrap a promise based API around a function that deals in callbacks.
Now you have a promise you can await the return value of findForUser.
You can't use async and await instead of creating a promise.

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

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)
]);

Async/await inside for...of vs. map

I'm trying to figure out why promises seem to work differently inside of for...of vs. map().
data is an array of objects of the form {app: MY_APP, link: MY_LINK}. I'm trying to convert it to the form {app: MY_APP, status: true OR false}}. The function checkLink() is async because it uses node-fetch and basically returns whether the given link is 200. Using for...of, I get the desired object:
let objToSend = [];
for (obj of data) {
objToSend.push({ app: obj.app, status: await checkLink(obj.link) });
}
// returns [{app: MY_APP, status: true}, ...]
But using map, as follows, I get an array of pending promises instead. Any help would be appreciated:
let objToSend = data.map(async obj => {
return {
app: obj.app,
status: await checkLink(obj.link)
};
});
// returns [ Promise { <pending> }, ...]
I also tried doing Promise.all(objToSend) after the map code, but it instead returns Promise { <pending> }
I'll leave the explanation up to the other answers, but just want to point out that there is also an performance difference.
You first solution waits on every iteration for the promise to resolve. Only calling checkLink if the previous one has resolved. This is an sequential solution.
check link1 => wait => check link2 => wait => check link3 => wait
The second solution iterates over every elements and sends out requests, without waiting for promises to resolve (for this reason an array of promises is returned). If you wait for all promises to be resolved you find this solution is a lot faster, since the requests are send out in parallel.
check link1 => check link2 => check link3 => wait for all
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
async function checkLink(link) {
await sleep(Math.random() * 1000 + 500); // sleep between 500 and 1500 ms
console.log(link);
return `status #${link}`;
}
(async function () {
const data = new Array(5).fill().map((_, index) => ({ app: "app", link: index }));
let objToSend;
console.log("solution #1");
objToSend = [];
for (let obj of data) {
objToSend.push({ app: obj.app, status: await checkLink(obj.link) });
}
console.log(objToSend);
console.log("==============================================");
console.log("solution #2");
objToSend = await Promise.all(data.map(async obj => {
return {
app: obj.app,
status: await checkLink(obj.link)
};
}));
console.log(objToSend);
})();
In the snippet the first solution takes 500/1500 * 5 = 2500/7500 between 2500 and 7500 ms. While the second solution takes between 500 and 1500 ms (depending on the slowest value to resolve).
Async functions always returns Promises. In your map function, as the callback returns promises, an array of promises is being created.
To convert it to your format, use it with Promise.all():
(async()=>{
let objToSend = await Promise.all(data.map(async obj => {
return {
app: obj.app,
status: await checkLink(obj.link)
};
}));
console.log(objToSend) //[{app: MY_APP, status: true}, ...]
})()
The await always halts the execution of the async function it is in, it doesn't work differently in both cases. In the later example however, you do call some async functions, and those calls will evaluate to promises, whereas in your first example, all those awaits are in the same async function.
I also tried doing Promise.all(objToSend) after the map code, but it instead returns Promise { <pending> }
Yup, await that and you get an array of results.

Can sequelize.transaction() take async function as callback?

So reading through the Sequelize documentation on Instance and also docs about transactions, the sequelize.transaction() takes autoCallback function as parameter. The docs say:
The callback is called with the transaction object, and should return
a promise. If the promise is resolved, the transaction commits; if the
promise rejects, the transaction rolls back
However I plan to do a lot of things inside on transaction and I want to avoid the callback hell. So I tried to make
try {
let result = sequelize.transaction({
isolationLevel: 'SERIALIZABLE'
}, async t => {
// code to run here with await
let var1 = await Model.find({ transaction: t });
let var2 = await Model.find({ transaction: t });
if (var1.id === 1)
return "Whatever result";
else
throw new Error("Something wicked");
}
)});
// Whatever result
console.log(result);
}catch(e){
// Something wicked
}
This seems to be working perfectly fine. But it is totally undocumented and I havent seen anybody use this. Is it ok, or will I face random issues?
As #Bergi and #Nicholas Tower pointed out in comments:
Since async function ALWAYS returns promise and the callback function should return a promise, this is rather straight-forward answer.
Yes, it can be used.
You are mixing styles, if you want to use straight async/await see below. You are also not passing the transaction (t in your code) to the query, so they won't use it - you have to pass it explicitly in the transaction option. If you make any changes after you should commit the transaction, and subsequently roll it back if there are any errors. You can also improve your code by running the queries concurrently with Promise.all().
// define outside the try/catch so you can rollback if needed
let transaction;
let result;
try {
transaction = await sequelize.transaction({ isolationLevel: 'SERIALIZABLE' });
// run concurrently with Promise.all()
const [ var1, var2 ] = await Promise.all([
// don't await here, but you have to pass the transaction
Model.findByPk({ transaction }),
Model.findOne({ transaction }), // ditto
]);
if (var1.id === 1) {
result = "Whatever result";
} else {
throw new Error("Something wicked");
}
// if you need to commit anything...
// await transaction.commit();
} catch(err) {
/* if you need to roll back anything...
if (transaction) {
await transaction.rollback();
}
*/
console.log(err);
}
return result;

Categories

Resources