So I have code that looks like this in my controller.
const Users = require("../models/users");
class UserController() {
getUserById(req, res) {
const id = req.params.id;
const users = new Users();
const userData = users.getUserById(uid).then(function(result) {
//Do other logic here that depends on db result
return res.status(200).json({status:"success", user: result});
});
}
}
My question is, what's the difference between that and the following, and which is the better practice?
const Users = require("../models/users");
class UserController() {
async getUserById(req, res) {
const id = req.params.id;
const users = new Users();
const userData = await users.getUserById(uid);
return res.status(200).json({status:"success",user:userData});
}
}
The second function call is using async-await feature of ES8, This makes our code appear as if it is synchronous and clean also, without doing .then() and callback within it. Both fetch() and async-await work on promise though.
Aync-Await is much more readable if I can put it this way. No call backs used here. It simply waits for the first API call to happen and only when the data arrives (or Promise is resolved) we fire another API call. For programmer itself life becomes little easy with Async-Await syntax.
For Error handling you will need try{} catch{} block in Aync-await but in case of fetch(), .catch() block handles API Errors for us.
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!
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 2 years ago.
I am getting into Node, and being thrown into the world of async programming.
I thoroughly read this article on Mozilla's site (along with many other links teaching about the async and await keywords):
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await
I have a project I am starting that will require numerous database calls, one after another. Some queries will rely upon the previous one's results. That being said, I decided I better practice and write some testing code.
Here is my testing code:
var users, items;
const getData = async function(){
// This is a DB call. Will take
// a little bit of time to complete.
setTimeout(() => {
var queryResults = "A list of items";
items = queryResults;
}, 1000);
return items;
}
const getUsers = async function(){
setTimeout(() => {
var queryResults = "A list of items";
users = queryResults;
}, 1000);
return users;
}
const init = async function(){
var itemSuccess = await getData();
var userSuccess = await getUsers();
console.log(`Here is your items: ${items}, and here are your users: ${users}`);
}
init();
My code failed with:
Here is your items: undefined, and here are your users: undefined
So naturally I headed back to Mozilla. I re-read the docs, and tried a few changes. Still, no success. Ultimately I ended up back at my original code above.
I cannot see why this is failing. I am simulating the time a query would take by using a timeout. If async does what it says, both functions should return their value, and then my console log should print out the correct results.
Does anyone see where I am going wrong?
Thank you.
Show us your REAL code with the actual database calls in it and we can help you much more specifically, including showing you working code. Please resist the temptation to post questions with pseudo-code. Show real code and we can help you so much faster and better if you show us the real code.
await ONLY does something useful when you await a promise that resolves or rejects when something you are interested in completes or errors.
It has no magic to somehow know when a a setTimeout() or any other asynchronous operation is done doing whatever it does.
So, when you do this:
var itemSuccess = await getData();
And, getData() looks like this:
const getData = async function(){
// This is a DB call. Will take
// a little bit of time to complete.
setTimeout(() => {
var queryResults = "A list of items";
items = queryResults;
}, 1000);
return items;
}
All you end up doing is:
var itemSuccess = await undefined
because your getData() function doesn't return a promise and, in fact, it doesn't even return the items because it returns BEFORE your timer so items (which doesn't appear to be declared properly here) has not yet received its value.
So, I would suggest you start back with the basics and go read what await really does. Make sure you understand how it interacts with a promise since it's only useful when used with a promise. And, then learn how to make your asynchronous operations return promises that are connected to when your asynchronous operations complete. Then, and only then, can you make proper use of await.
In a well organized world, you make sure that all your asynchronous operations are already returned promises and then you can async/await and .then() and .catch() to control the flow of all your asynchronous operations. In many cases, this involves learning the promise interface for whatever you're using. For example, modern versions of node.js have an entire promise interface for the file system in fs.promises.
In a few cases, there may not exist a promise interface for some library you're using and you may have to wrap the asynchronous operation in a promise to make your own promise interface. util.promisify() is built-into node.js to help you do that.
If you show the ACTUAL code, include which database you're using and the actual database call you're trying to make, then someone here can probably help you with the proper way to use the promise interface on the database. This pseudo code prevents us from helping you that specifically. Without your actual code and knowing the actual DB calls you're making in a specific DB, it's hard for us to advise much more than go use the promise interface to your database and learn how to use those promises properly. Happy to help more specifically, but we need to see you REAL code to do that.
And, lastly in Javascript if you're assigning a value to something in a higher scope from within any asynchronous callback, there's a 99.99% chance you're doing things wrong. The only place you can actually use that value is inside the callback itself or in a function that you call from there and pass the value to. So, when you do items = queryResults, where items is declared in a couple scopes above you, then alarm bells should go off that this is not going to work for you. It's not how you program with asynchronous code in Javascript, not just for style reasons, but because it doesn't work.
Use:
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
instead of
setTimeout(() => {
}, 1000);
await waits for a Promise to resolve.
// normal function that returns a promise
const getData = function(){
// This is a DB call. Will take
// a little bit of time to complete.
return new Promise(resolve => {
setTimeout(() => {
var queryResults = "A list of data";
resolve(queryResults);
}, 1000);
});
}
const getUsers = function(){
return new Promise(resolve => {
setTimeout(() => {
var queryResults = "A list of users";
resolve(queryResults);
}, 1000);
});
}
const init = async function(){
var itemSuccess = await getData();
var userSuccess = await getUsers();
console.log(`Here is your items: ${itemSuccess}, and here are your users: ${userSuccess}`);
}
init();
An async function does 2 things.
it makes it possible to use the await keyword to wait for promises.
it makes the function automatically return a promise itself that wraps whatever the function returns
async function foo() {
return 123;
}
const v = foo();
console.log(v instanceof Promise);
v.then(value => console.log(value));
A more normal database query might look something like this
async function dbQuery(queryUrl) {
const res = await fetch(queryUrl); // fetch returns a promise that resolves to a Response
return res.json(); // res.json returns a promise that resolves to the body of response parsed as json.
}
async function main() {
const films = await dbQuery('https://greggman.github.io/doodles/test/data/films.json');
console.log(JSON.stringify(result, null, 2));
}
main();
so, i was using passport-jwt and come on to this problem. i have two models that are Employee with name, address, role, ... and Employee credential with username, passsword , employee(Object_Id).
Then what i wanted is that after i found the employee username and password then i need the employee role so i used employee.findbyId but it returns a promise and call the callback done before getting role of the employee.
what i want is why it is happening(detail about it for understanding)??? i didn't use async/await so the function is synchronous.
(passport)=>{
let options = {};
options.jwtFromRequest = ExtractJwt.fromAuthHeaderWithScheme('jwt');
options.secretOrKey = config.SECRET;
passport.use(new Strategy( options,(payload,done)=>{
Employee_credential.findById(payload.data._id, (err, employee)=>{
if(err) return done(err,false);
if(employee){
var result = Employee.getById(employee.employee).populate('role').catch(err=>{throw err})
employee.role = result.role.role_Id;
return done(null, employee);
}
return done(null,false);
})
}))
}
Things may be happening sequentially here, but not synchronously. Your functions are happening in the order as written, but it is not going to wait on the result of your API call. That is why you are getting a promise instead of the role of the employee. To make this work you should either use async/await (my preferred method) or return a promise and set up the next function with .then.
A note from personal experience, not all APIs are going to work properly with await depending on how they handle promises. In some cases, I've had to combine that with explicitly resolving the promise via return new Promise((resolve) => { //your function; resolve(employee); } or something like that.
I understand that this is a basic question, but I can't figure it out myself, how to export my variable "X" (which is actually a JSON object) out of "for" cycle. I have tried a various ways, but in my case function return not the JSON.object itself, but a "promise.pending".
I guess that someone more expirienced with this will help me out. My code:
for (let i = 0; i < server.length; i++) {
const fetch = require("node-fetch");
const url = ''+(server[i].name)+'';
const getData = async url => {
try {
const response = await fetch(url);
return await response.json();
} catch (error) {
console.log(error);
}
};
getData(url).then(function(result) { //promise.pending w/o .then
let x = result; //here is real JSON that I want to export
});
}
console.log(x); // -element is not exported :(
Here's some cleaner ES6 code you may wish to try:
const fetch = require("node-fetch");
Promise.all(
server.map((srv) => {
const url = String(srv.name);
return fetch(url)
.then((response) => response.json())
.catch((err) => console.log(err));
})
)
.then((results) => {
console.log(results);
})
.catch((err) => {
console.log('total failure!');
console.log(err);
});
How does it work?
Using Array.map, it transforms the list of servers into a list of promises which are executed in parallel. Each promise does two things:
fetch the URL
extract JSON response
If either step fails, that one promise rejects, which will then cause the whole series to reject immediately.
Why do I think this is better than the accepted answer? In a word, it's cleaner. It doesn't mix explicit promises with async/await, which can make asynchronous logic muddier than necessary. It doesn't import the fetch library on every loop iteration. It converts the server URL to a string explicitly, rather than relying on implicit coercion. It doesn't create unnecessary variables, and it avoids the needless for loop.
Whether you accept it or not, I offer it up as another view on the same problem, solved in what I think is a maximally elegant and clear way.
Why is this so hard? Why is async work so counterintuitive?
Doing async work requires being comfortable with something known as "continuation passing style." An asynchronous task is, by definition, non-blocking -- program execution does not wait for the task to complete before moving to the next statement. But we often do async work because subsequent statements require data that is not yet available. Thus, we have the callback function, then the Promise, and now async/await. The first two solve the problem with a mechanism that allows you to provide "packages" of work to do once an asynchronous task is complete -- "continuations," where execution will resume once some condition obtains. There is absolutely no difference between a boring node-style callback function and the .then of a Promise: both accept functions, and both will execute those functions at specific times and with specific data. The key job of the callback function is to act as a receptacle for data about the asynchronous task.
This pattern complicates not only basic variable scoping, which was your main concern, but also the issue of how best to express complicated workflows, which are often a mix of blocking and non-blocking statements. If doing async work requires providing lots of "continuations" in the form of functions, then we know that doing this work will be a constant battle against the proliferation of a million little functions, a million things needing names that must be unique and clear. This is a problem that cannot be solved with a library. It requires adapting one's style to the changed terrain.
The less your feet touch the ground, the better. :)
Javascript builds on the concept of promises. When you ask getData to to do its work, what is says is that, "OK, this is going to take some time, but I promise that I'll let you know after the work is done. So please have faith on my promise, I'll let you know once the work is complete", and it immediately gives you a promise to you.
That's what you see as promise.pending. It's pending because it is not completed yet. Now you should register a certain task (or function) with that promise for getData to call when he completes the work.
function doSomething(){
var promiseArray = [];
for (let i = 0; i < server.length; i++) {
const fetch = require("node-fetch");
const url = ''+(server[i].name)+'';
const getData = async url => {
try {
const response = await fetch(url);
return await response.json();
} catch (error) {
console.log(error);
}
};
promiseArray.push(getData(url)); // keeping track of all promises
}
return Promise.all(promiseArray); //see, I'm not registering anything to promise, I'm passing it to the consumer
}
function successCallback(result) {
console.log("It succeeded with " + result);
}
function failureCallback(error) {
console.log("It failed with " + error);
}
let promise = doSomething(); // do something is the function that does all the logic in that for loop and getData
promise.then(successCallback, failureCallback);
I will appreciate if you help me with the following case:
Given function:
async function getAllProductsData() {
try {
allProductsInfo = await getDataFromUri(cpCampaignsLink);
allProductsInfo = await getCpCampaignsIdsAndNamesData(allProductsInfo);
await Promise.all(allProductsInfo.map(async (item) => {
item.images = await getProductsOfCampaign(item.id);
}));
allProductsInfo = JSON.stringify(allProductsInfo);
console.log(allProductsInfo);
return allProductsInfo;
} catch(err) {
handleErr(err);
}
}
That function is fired when server is started and it gathers campaigns information from other site: gets data(getDataFromUri()), then extracts from data name and id(getCpCampaignsIdsAndNamesData()), then gets products images for each campaign (getProductsOfCampaign());
I have also express.app with following piece of code:
app.get('/products', async (req, res) => {
if (allProductsInfo.length === undefined) {
console.log('Pending...');
allProductsInfo = await getAllProductsData();
}
res.status(200).send(allProductsInfo);
});
Problem description:
Launch server, wait few seconds until getAllProductsData() gets executed, do '/products' GET request, all works great!
Launch server and IMMEDIATELY fire '/products' GET request' (for that purpose I added IF with console.log('Pending...') expression), I get corrupted result back: it contains all campaigns names, ids, but NO images arrays.
[{"id":"1111","name":"Some name"},{"id":"2222","name":"Some other name"}],
while I was expecting
[{"id":"1111","name":"Some name","images":["URI","URI"...]}...]
I will highly appreciate your help about:
Explaining the flow of what is happening with async execution, and why the result is being sent without waiting for images arrays to be added to object?
If you know some useful articles/specs part covering my topic, I will be thankful for.
Thanks.
There's a huge issue with using a global variable allProductsInfo, and then firing multiple concurrent functions that use it asynchronously. This creates race conditions of all kinds, and you have to consider yourself lucky that you got only not images data.
You can easily solve this by making allProductsInfo a local variable, or at least not use it to store the intermediate results from getDataFromUri and getCpCampaignsIdsAndNamesData - use different (local!) variables for those.
However, even if you do that, you're potentially firing getAllProductsData multiple times, which should not lead to errors but is still inefficient. It's much easier to store a promise in the global variable, initialise this once with a single call to the info gathering procedure, and just to await it every time - which won't be noticeable when it's already fulfilled.
async function getAllProductsData() {
const data = await getDataFromUri(cpCampaignsLink);
const allProductsInfo = await getCpCampaignsIdsAndNamesData(allProductsInfo);
await Promise.all(allProductsInfo.map(async (item) => {
item.images = await getProductsOfCampaign(item.id);
}));
console.log(allProductsInfo);
return JSON.stringify(allProductsInfo);
}
const productDataPromise = getAllProductsData();
productDataPromise.catch(handleErr);
app.get('/products', async (req, res) => {
res.status(200).send(await productDataPromise);
});
Of course you might also want to start your server (or add the /products route to it) only after the data is loaded, and simply serve status 500 until then. Also you should consider what happens when the route is hit after the promise is rejected - not sure what handleErr does.