Old code with promises
I have some old code using mongoose. It fetches some information from the database. AccountViewPermission is a schema. All good. I am accessing it with a .then which makes it behave like a Promise.
AccountViewPermission.find({ requested_by: request.body.credentials.character.id})
.then( permissions => {
response.status(200).json(permissions)
})
Documentation mentions await capability
According to the documentation I should also be able to use asyn/await:
Mongoose queries are not promises. They have a .then() function for co and async/await as a convenience. However, unlike promises, calling a query's .then() can execute the query multiple times.
But using await throws an error
So I started writing this code with an await instead of a (wannabe) promise.
const permissions = await AccountViewPermission.find({ requested_by: request.body.credentials.character.id})
response.status(200).json(permissions)
However now node.js throws me an error: SyntaxError: await is only valid in async function. Which is true, but the docs say mongoose should still be able to use await.
How can I use the await syntax in this scenario?
As stated in the official docs "The await operator is used to wait for a Promise. It can only be used inside an async function." Docs: await-operator
Mongoose cant change the way javascript deals with async / await keywords. if you need follow-up information check the docs.
To make your code work you HAVE to wrap it inside a async function. There is no way around this.
async function doSomething() {
const permissions = await AccountViewPermission.find({
requested_by: request.body.credentials.character.id
})
response.status(200).json(permissions)
}
Related
I have 3 async functions:
ToDoItem.deleteMany({}); // deletes entire collection
ToDoItem.insertMany(itemArray); // adds new items to collection
ToDoItem.find({}); // finds all the items in the collection
This code alone doesn't work well, as they do not follow a consistent order. I.e. the insertion might happen before deletion, which I do not want.
I can use callbacks to chain them together (callback hell), and I can also use .then to chain them together, as they return promises. However, I would like to use async/await.
Additionally, these functions can be given optional callbacks, for instance:
ToDoItem.find({}, (data) => {
console.log(data);
});
This is useful as I want to see all the data in my DB that matches the query {} (which is all items).
However I can't figure out how to access these callbacks by using async and await. I can do it via callbacks or .then, but the code is more messy. Is there a way to do this?
Edit:
As per Bergi's reply, I have edited my code as such:
async function setupDatabase() {
const deleteResult = await ToDoItem.deleteMany({});
console.log("Items deleted. Delete result:")
console.log(deleteResult);
const insertResult = await ToDoItem.insertMany(defaultItems);
console.log("Items have been added successfully");
console.log(insertResult);
const findResult = await ToDoItem.find({});
console.log("Here are the items:")
console.log(findResult);
}
Am I correct in thinking that:
deleteResult will now evaluate to be either the deletion confirmation (if successful) or the error (if rejected). And similarly with insertResult and findResult?
What do I do if I want to return the collection found by .find({}), as the function setupDatabase is now async and returns a promise.
If 1) is correct, how do I separate out when I'm getting an error and when I'm getting a result?
As per Konrad's response, I have done the following:
async function setupDatabase() {
const deleteResult = await ToDoItem.deleteMany({});
console.log("Items deleted. Delete result:")
console.log(deleteResult);
const insertResult = await ToDoItem.insertMany(defaultItems);
console.log("Items have been added successfully");
console.log(insertResult);
const findResult = await ToDoItem.find({});
console.log("Here are the items:")
console.log(findResult);
return findResult;
}
app.get("/", function(req, res) {
(async function() {
const objectList = await setupDatabase();
let dataList = [];
for (element of objectList) {
dataList.push(element.todo);
}
res.render("list", {listTitle: "Today", newListItems: dataList});
}());
My idea was to return the findResult inside the setupDatabase function. But this is actually a promise since the function is async, so I wrapped it in an IIFE inside the .get. I then iterated over this list and created dataList which has the actual data I want to render.
You don't use async/await with callbacks.
You use await with promises, and you should not mix those with callbacks - in particular, MongoDB does not return a promise when you pass a callback. You need to write
await ToDoItem.deleteMany({}); // deletes entire collection
await ToDoItem.insertMany(itemArray); // adds new items to collection
const data = await ToDoItem.find({});
Am I correct in thinking that [the await expression] will now evaluate to be either the deletion confirmation (if successful) or the error (if rejected)?
No. The result of the await is the successful promise fulfillment value. In case of a promise rejection, the error is thrown as an exception, you can handle it with a try/catch block.
What do I do if I want to return the collection found by .find({}), as the function setupDatabase is now async and returns a promise.
The async/await syntax doesn't change anything. The function was asynchronous before, and it's impossible to immediately return the result, regardless whether you'd use callbacks or promise .then() chaining syntax or await syntax. Returning a promise is actually desirable - the caller just has to wait for it!
My code works with the following:
export async function FetchAPI(props){
const url = `www.website.com/${props}`
let res = await fetch(url)
let response = await res.json()
return response
}
But when I try to clean up the code using better practices, I get error for the below code saying it is returning undefined.
export async function FetchAPI(props){
const url = `www.website.com/${props}`
fetch(url)
.then((resp => {
resp.json()
return resp
}))
}
Anyone understand the differences and how I can get the second one to work?
The correct code for the .then() version is this:
export function FetchAPI(props){
const url = `http://www.website.com/${props}`;
return fetch(url).then(resp => {
return resp.json();
});
}
Is the return type using .then different from without it?
This fixed version would generate the same exact returned results as your await version with the same URL. Both would return a promise that would resolve/reject based on the results of the fetch and the JSON conversion of the body.
Note: URLs used in Javascript programming should contain the protocol as shown here.
I wonder why using thenable rather than async/await is better practice.
Async/await is a more modern syntax. When sequencing multiple asynchronous operations or branching based on asynchronous results, it is pretty much always simpler to write, read and debug with async/await than using .then().
When you just have a single asynchronous operation, you can sometimes just return that promise and using async/await doesn't necessarily provide any advantages. It's entirely up to the designer of the code which one to use - either can work just fine when written properly.
There are times when a simple .catch() is cleaner than putting a try/catch around the whole block too for localized error handling. Like many things with coding style, there's no absolute right and wrong, just opinions on what looks like the cleanest code.
Also, if you want your code to run in older Javascript environments that don't support async/await, then you may need to use .then() and .catch() or transpile your code to an older target.
For a related discussion see Async / await vs then which is the best for performance?.
I've been looked on StackOverflow and haven't seen any direct examples of what I'm asking. I'm reading this article on memoization link here if you want to look.
It appears to me that you should be able to run them all together and use the return value from getSoupRecipe() as input for hireSoupChef()
async function makeSoupFromType(soupType) {
let [ soupRecipe, soupPan, soupChef ] = await Promise.all([
getSoupRecipe(soupType),
buySoupPan(),
hireSoupChef(soupRecipe.requiredSkills)
]);
return await makeSoup(soupChef, soupRecipe, soupPan);
}
So the question is can all three async functions run at the same time and once getSoupRecipe returns I use it's variable name (which is soupRecipe) as input for hireSoupChef.
I would post all the other code here for you to see but I think it would probably make the question look too daunting, so the link is above. You don't necessarily have to look at it to understand I don't think because I think I've stated the question right, but nonetheless if you want to you can.
Not by itself, no. In your example the soupRecipe (and the other two variables) are only initialised after the Promise.all(…) has been awaited, so it can't be used in the expressions that are passed to Promise.all as an argument. There's no magic going on here, Promise.all is not part of the syntax but really just returning one promise that fulfills with an array once.
However, the approach outlined in this answer to How do I access previous promise results in a .then() chain? does enable the desired chaining of asynchronous actions:
async function makeSoupFromType(soupType) {
const soupRecipePromise = getSoupRecipe(soupType);
const [soupRecipe, soupPan, soupChef] = await Promise.all([
soupRecipePromise,
buySoupPan(),
soupRecipePromise.then(({requiredSkills}) => hireSoupChef(requiredSkills))
]);
return makeSoup(soupChef, soupRecipe, soupPan);
}
Alternatively, with plain async/await you could also use an IIFE to avoid the then() call:
async function makeSoupFromType(soupType) {
const [soupPan, [soupRecipe, soupChef]] = await Promise.all([
buySoupPan(),
(async () => {
const soupRecipe = await getSoupRecipe(soupType);
const soupChef = await hireSoupChef(soupRecipe.requiredSkills);
return [soupRecipe, soupChef];
})(),
]);
return makeSoup(soupChef, soupRecipe, soupPan);
}
Could you please help me to understand javascirpt async hell?
I think I am missing something important ☹ The thing is that js examples and most of the answers on the internet are related to just one part of code – a small snippet. But applications are much more complicated.
I am not going write it directly in JS since I am more interested of the design and how to write it PROPERLY.
Imagine these functions in my application:
InsertTestData();
SelectDataFromDB_1(‘USERS’);
SelectDataFromDB_2(‘USER_CARS’,’USERS’);
FillCollections(‘USER’,’USER_CARS’);
DoTheWork();
DeleteData();
I did not provide any description for the functions but I think it is obvious based on names. They need to go in THIS SPECIFIC ORDER. Imagine that I need to run a select into the db to get USERS and then I need run a select to get USER_CARS for these USERS. So it must be really in this order (consider the same for other functions). The thing is that need to call 6 times Node/Mysql which is async but I need results in specific order. So how can I PROPERLY make that happen?
This could work:
/* not valid code I want to present the idea and keep it short */
InsertTestData(
Mysql.query(select, data, function(err,success)
{
SelectDataFromDB_1(‘USERS’); -- in that asyn function I will call the next procedure
}
));
SelectDataFromDB_1 (
Mysql.query(select, data, function(err,success)
{
SelectDataFromDB_2(‘USERS’); -- in that asyn function I will call the next procedure
}
));
SelectDataFromDB_2 (
Mysql.query(select, data, function(err,success)
{
FillCollections (‘USERS’); -- in that asyn function I will call the next procedure
}
));
etc..
I can “easily” chain it but this looks as a mess. I mean really mess.
I can use some scheduler/timmers to schedule and testing if the previous procedure is done)
Both of them are mess.
So, what is the proper way to do this;
Thank you,
AZOR
If you're using a recent version of Node, you can use ES2017's async/await syntax to have synchronous-looking code that's really asynchronous.
First, you need a version of Mysql.query that returns a promise, which is easily done with util.promisify:
const util = require('util');
// ...
const query = util.promisify(Mysql.query);
Then, wrap your code in an async function and use await:
(async () => {
try {
await InsertTestData();
await SelectDataFromDB_1(‘USERS’);
await SelectDataFromDB_2(‘USER_CARS’,’USERS’);
await FillCollections(‘USER’,’USER_CARS’);
await DoTheWork();
await DeleteData();
} catch (e) {
// Handle the fact an error occurred...
}
})();
...where your functions are async functions, e.g.:
async InsertTestData() {
await query("INSERT INTO ...");
}
Note the try/catch in the async wrapper, it's essential to handle errors, because otherwise if an error occurs you'll get an unhandled rejection notice (and future versions of Node may well terminate the process). (Why "unhandled rejections"? Because async functions are syntactic sugar for promises; an async function returns a promise.) You can either do that with the try/catch shown, or alternate by using .catch on the result of calling it:
(async () => {
await InsertTestData();
// ...
})().catch(e => {
// Handle the fact an error occurred
});
Why does using await need its outer function to be declared async?
For example, why does this mongoose statement need the function it's in to return a promise?
async function middleware(hostname, done) {
try {
let team = await Teams.findOne({ hostnames: hostname.toLowerCase() }).exec();
done(null, team);
} catch (err) { done(err); }
}
I see the runtime/transpiler resolving the Teams promise to it's value and async signaling it "throws" rejected promises.
But try/catch "catches" those rejected promises, so why are async and await so tightly coupled?
I'm not privy to the JavaScript language design discussions, but I assume it's for the same reasons that the C# language requires async (also see my blog).
Namely:
Backwards compatibility. If await was suddenly a new keyword everywhere, then any existing code using await as a variable name would break. Since await is a contextual keyword (activated by async), only code that intends to use await as a keyword will have await be a keyword.
Easier to parse. async makes asynchronous code easier to parse for transpilers, browsers, tools, and humans.
Copied from https://stackoverflow.com/a/41744179/1483977 by #phaux:
These answers all give valid arguments for why the async keyword is a
good thing, but none of them actually mentions the real reason why it
had to be added to the spec.
The reason is that this was a valid JS pre-ES7
function await(x) {
return 'awaiting ' + x
}
function foo() {
return(await(42))
}
According to your logic, would foo() return Promise{42} or
"awaiting 42"? (returning a Promise would break backward
compatibility)
So the answer is: await is a regular identifier and it's only
treated as a keyword inside async functions, so they have to be marked
in some way.
Fun fact: the original spec proposed more lightweight function^ foo()
{} for async syntax.
Because using await inside middleware function means the middleware function can't return a result immediately (it must wait until await is settled) and middleware function callers must wait until the promise (returned from middleware function) is settled.