I'm using an API which has a rate limit of 500 requests / min.
Therefore I decided to use bottleneck. But I need to execute array of async functions which generates a Promise to make that API call. I'm not sure I'm on the right way. Because API responses me with "Exceeded rate limit of 83 in 10_seconds" where I just only send 70 requests in 10 seconds.
Here is how I call the main function:
const result = await Helper.updateUsers(request.query.where);
..
..
Here is the helper.js
const Boom = require("boom");
const mongoose = require("mongoose");
const Bottleneck = require("bottleneck");
const Intercom = require("intercom-client");
const config = require("../../config/config");
const client = new Intercom.Client({
token: config.intercom.access_token
});
const User = mongoose.model("User");
const Shop = mongoose.model("Shop");
// create a rate limiter that allows up to 70 API calls per 10 seconds,
// with max concurrency of 70
const limiter = new Bottleneck({
maxConcurrent: 70,
minTime: 10000
});
// Helpers
// This function prepares a valid Intercom User Object.
// user -> User Object
// returns <Promise>
const prepareAndUpdateUser = async user => {
try {
let userData = {
email: user.email,
user_id: user._id,
companies: []
};
Shop.find({ _id: { $in: user.account.shops } })
.exec((err, shops) => {
if (err) console.log("INTERCOM UPDATE USER", err);
shops.forEach(shop => {
let shopData = {
company_id: shop._id,
name: shop.name[shop.defaultLanguage.code]
};
userData.companies.push(shopData);
});
// Update Intercom Promise
return client.users.create(userData);
});
} catch (e) {
return Boom.boomify(err);
}
};
module.exports.updateUsers = async query => {
try {
const users = await User.find(query)
.populate("account")
.limit(700);
if (users && users.length > 0) {
limiter.schedule(() => {
const allTasks = users.map(
async user => await prepareAndUpdateUser(user)
);
return Promise.all(allTasks);
});
return users.length;
} else {
return 0;
}
} catch (err) {
return Boom.boomify(err);
}
};
Am I using Bottleneck & Async-Await correct?
The first thing to point out is your use of callbacks in an async method instead of awaiting a promise. You should use the promise returning version of Shops.find() and await the results.
async function prepareAndUpdateUser(user) {
try {
const shops = await Shop.find({ _id: { $in: user.account.shops } }).exec();
return client.users.create({
email: user.email,
user_id: user._id,
companies: shops.map(shop => {
return {
company_id: shop._id,
name: shop.name[shop.defaultLanguage.code]
};
})
});
} catch (e) {
return Boom.boomify(err);
}
}
In your updateUsers method you're using the rate limiter backwards. You want to map the users into the rate limiter so that it can control when prepareAndUpdateUser is called, currently you'll be requesting everything in parallel. You also want to wait for promise returned by the rate limiter to resolve. Essentially you'll want to move limiter.scehdule(...) into user.map(...).
async function updateUsers(query) {
try {
const users = await User.find(query)
.populate("account")
.limit(700);
if (users && users.length > 0) {
// Schedule an update for each user
const allTasks = users.map(user => {
// Schedule returns a promise that resolves when the operation is complete
return limiter.schedule(() => {
// This method is called when the scheduler is ready for it
return prepareAndUpdateUser(user)
})
});
// Wait for all the scheduled tasks to complete
await Promise.all(allTasks);
return users.length;
} else {
return 0;
}
} catch (err) {
return Boom.boomify(err);
}
}
Related
I have this route to delete a "garage" from the mongodb database and then grab some of the remaining garages. For some reason it is still returning the deleted garage and returning it, but if I check the database the delete was successful.
router.post('/garage/delete', requireLogin, async (req, res) => {
let limit = 20;
try {
let list = req.body;
list.map( async (item) => {
const existingGarage = await Garage.find({_id: item._id});
if (existingGarage) {
await Garage.deleteOne({_id: item._id});
} else {
res.status(400).send("Garage not found");
}
})
const allGarages = await Garage.find().limit( limit );
console.log(allGarages);
res.send(allGarages);
} catch {
res.status(400).send("Garage not found");
}
})
You will need to await all the promises returned by the map function.
Promise.all awaits an array of promises and runs them in parallel.
since you're passing an async function to the map function you will need to await all the promises returned by that async function
Other solution is to use a for of loop
router.post('/garage/delete', requireLogin, async (req, res) => {
let limit = 20;
try {
let list = req.body;
await Promise.all(list.map( async (item) => {
const existingGarage = await Garage.find({_id: item._id});
if (existingGarage) {
await Garage.deleteOne({_id: item._id});
} else {
res.status(400).send("Garage not found");
}
}))
const allGarages = await Garage.find().limit( limit );
console.log(allGarages);
res.send(allGarages);
} catch {
res.status(400).send("Garage not found");
}
})
I'm pretty new to graphql (and nodejs as well). I'm following a Udemy course on Apollo and mongo which has been going well mostly. However I can't get one of the resolvers to be called. Another resolver is working fine, and they appear to use the same layout. Also, the context is being called before the resolver that is not being called, so I know it's at least getting that far.
Here is the root server.js with the working context:
const resolvers = require('./resolvers');
...
const apolloServer = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => {
await verifyUser(req);
console.log("=== context ran, user email : ", req.email) ;
return {
email: req.email,
loggedInUserId: req.loggedInUserId
}
}
});
resolvers are modularized, and combined in a /resolvers/index.js, here:
const { GraphQLDateTime } = require('graphql-iso-date')
const userResolver = require('./user');
const taskResolver = require('./task');
const customDateScalarResolver = {
Date: GraphQLDateTime
}
module.exports = [
userResolver,
taskResolver,
customDateScalarResolver
]
and here is the tasks resolver, located at /resolvers/task.js, which is the one not being called:
const uuid = require('uuid')
const { combineResolvers } = require('graphql-resolvers');
const { users, tasks } = require('../constants');
const Task = require('../database/models/task');
const User = require('../database/models/user');
const { isAuthenticated, isTaskOwner } = require('./middleware');
module.exports = {
Query: {
tasks: async ( _, __, { loggedInUserId }) => {
console.log("tasks query, loggedInUserId : ", loggedInUserId);
try {
const tasks = await Task.find( { user: loggedInUserId });
return tasks;
} catch (error) {
console.log(error);
throw error;
}
},
task: async ( parent, { id }, ) => {
console.log("taskbyId query, id : ", id);
// tasks.find(task => task.id == args.id);
try {
const task = await Task.findById(id);
console.log("taskById query, found task? : ", task);
return task;
} catch (error) {
console.log(error);
throw error;
}
},
},
Mutation: {
// createTask: combineResolvers(isAuthenticated, async (_, { input }, { email }) => {
createTask: async (_, { input }, { email }) => {
try {
console.log("creating task, email : ", email);
const user = await User.findOne({ email });
const task = new Task({ ...input, user: user.id });
const result = await task.save();
user.tasks.push(result.id);
await user.save();
return result;
} catch (error) {
console.log(error);
throw error;
}
}
// )
},
Task: {
user: async ( parent ) => {
console.log("in task.user field resolver");
try {
const user = await User.findById(parent.user);
return user;
} catch (error) {
console.log(error);
throw error;
}
}
},
}
When I run the tasks query, the console.log from the context setup function logs 3 times, but does NOT log the console.log line from the tasks resolver. It also appears to not return at all. I'm just using the default graphiql web client. The verifyUser() does find a return a user, so I know the db connection is working fine as well.
mergeResolvers should be used to merge resolvers.
It's designed to merge different [entities] object [/structured] resolvers before use [as one tree structured] in server [config].
F.e. it merges/combines respectively [by type] Query resolvers from users resolver with tasks Query resolvers ... and Mutation resolvers from users resolver with tasks Mutation resolvers.
I am trying to search available objects of a MongoDB collection(e.g. ParkingSpot) using specific business logic, the problem is that inside this function which is an async/await func I am looking inside another collection with .find() which I use as an async/await as well. The second function doesn't execute and gets back as an promise " Promise { } " and therefore the business logic is not applied. Can you help me please?
Here is 1st function:
exports.get_parkingSpots_avail_for_parking = async (req, res, next) => {
try {
const radius = req.body.radius;
const userLatitude = req.body.userLatitude;
const userLongitude = req.body.userLongitude;
const reqStartDate = new Date(req.body.reqStartDate);
const reqEndDate = new Date(req.body.reqEndDate);
const parkingSpots = await ParkingSpot.find({
isAvailable: true
}, (err, parkingSpots) => {
if (err) {
return res.status(500).json({
error: err
});
}
const freeParkingSpots = parkingSpots.filter(parkingSpot => {
return distanceCalculator(parkingSpot.latitude, parkingSpot.longitude, userLatitude, userLongitude) < radius
})
const availParkingSpots = freeParkingSpots.filter( freeParkingSpot => {
// console.log(freeParkingSpot._id);
console.log(isParkingAvailable(reqStartDate, reqEndDate, freeParkingSpot._id));
return isParkingAvailable(reqStartDate, reqEndDate, freeParkingSpot._id);
})
}).select('_id userId number address latitude longitude notes isAvailable startOfAvailability endOfAvailability');
console.log(parkingSpots);
if (parkingSpots.length > 0) {
return res.status(200).json({
parkingSpots: parkingSpots
});
}
return res.status(404).json({
message: "No available parking spots for requeste period"
});
} catch (err) {
res.status(500).json({
error: err
})
}
};
Second function which is being called :
module.exports = async function isParkingAvailable(reqStartDate, reqEndDate, parkingSpotId) {
const parkingSpotBookings = await Booking.find({ parkingSpotId: parkingSpotId})
.select('_id parkingSpotId sharerUserId renterUserId startDate endDate');
if (parkingSpotBookings.length == 0) {
return true;
}
parkingSpotBookings.filter(booking => {
//console.log(parkingSpotId);
//console.log("Is overlapping" +!isOverlapping(reqStartDate, reqEndDate, booking.startDate, booking.endDate));
return !isOverlapping(reqStartDate, reqEndDate, booking.startDate, booking.endDate)
})
}
So the problem is that calling second function appears as that in console.log :Promise { }
Await will Return the result to your parkingSpot variable.
For the second function:
You have defined this function as an async, that means it is holding asynchronous process, and in Node JS es6 async process is processed as a promise, so if you won't call it with await it will return a promise only
exports.get_parkingSpots_avail_for_parking = async (req, res, next) => {
try {
const radius = req.body.radius;
const userLatitude = req.body.userLatitude;
const userLongitude = req.body.userLongitude;
const reqStartDate = new Date(req.body.reqStartDate);
const reqEndDate = new Date(req.body.reqEndDate);
const parkingSpots = await ParkingSpot.find({isAvailable: true}).select('_id userId number address latitude longitude notes isAvailable startOfAvailability endOfAvailability');
const freeParkingSpots = parkingSpots.filter(parkingSpot => {
return distanceCalculator(parkingSpot.latitude, parkingSpot.longitude, userLatitude, userLongitude) < radius
});
const availParkingSpots = freeParkingSpots.filter( async freeParkingSpot => {
/* You have defined this function as an async, that means it is holding asynchronous process, and in
Node JS es6 async process is processed as a promise, so if you won't call it with await it will return a promise only */
return await isParkingAvailable(reqStartDate, reqEndDate, freeParkingSpot._id);
});
if (parkingSpots.length > 0) {
return res.status(200).json({
parkingSpots: parkingSpots
});
}
return res.status(404).json({
message: "No available parking spots for requeste period"
});
} catch (err) {
res.status(500).json({
error: err
})
}
};
I hope it will help you.
Thank you
I'm using Nodejs with MongoDB(mongoose along with express).
Since I don't trust the user data, I need to verify it from the database.
input data:
{
"id": "someid",
"nottrusteddata": [ {"id": "1"}, {"id" :"2"}]
}
In my function, I'm verifying the data:
router.post("/validate", (req, res,next) =>{
let validated_data = validate_data(req);
console.log(JSON.stringify(validated_data));
const mydata = new Mydata({
id: req.body.id,
lst : validated_data
});
console.log("mydata: " + JSON.stringify(mydata));
/* Some Usefull stuff is here */
res.status(200).json();
}
function validate_data(req){
let validated_data = []
for(let i = 0; i < req.body.nottrusteddata.length; i++)
{
Databaseobject.findOne({'id': req.body.nottrusteddata[i].id})
.exec()
.then(dbobject =>{
if(dbobject) // not undefined, it exists in the database
{
// Some logic with the object returned from the database
let tmp_object = {};
tmpobject.id = dbobject.id;
// Append it to the list, so that the upper function can use it
validated_data.push(tmp_object);
}
})
}
return validated_data;
}
The desired output should contain the correct information coming from the database, however, due to the async nature of the nodejs, validated_data returns null.
I have also tried using Promise. I couldn't succeed it.
const validate_data = function(req){
return new Promise(function(resolve,reject){
let validated_data = []
for(let i = 0; i < req.body.nottrusteddata.length; i++)
{
Databaseobject.findOne({'id': req.body.nottrusteddata[i].id})
.exec()
.then(dbobject =>{
if(dbobject) // not undefined, it exists in the database
{
let tmp_object = {};
tmpobject.id = dbobject.id;
validated_data.push(tmp_object);
}
})
}
resolve(validated_data);
}
}
What am I doing wrong? How can I wait for the database query to finish, then execute the main part? If there is only one validation, I could've used .then(). However, the list might have contained many elements and I need to wait for all of them to be verified.
Your Databaseobject.findOne() calls are asynchronous so your promise will resolve before any of them complete.
You can make use of Promise.all to wait until all of your promises resolve.
Hopefully, this will work for you:
router.post("/validate", (req, res) => {
validate_data(req.body.nottrusteddata)
.then(validated_data => {
const mydata = new Mydata({
id: req.body.id,
lst: validated_data
})
// Some useful stuff is here
res.status(200).json()
})
.catch(err => {
// Handle error
})
}
function validate_data(nottrusteddata) {
// Create array of pending promises
const promises = nottrusteddata
.map(item => {
return Databaseobject
.findOne({ 'id': item.id })
.exec()
})
// Wait for all promises to resolve
return Promise.all(promises)
.then(docs => {
return docs
.filter(dbobject => dbobject) // Filter out undefined
.map(dbobject => {
return { id: dbobject.id }
})
})
}
If you want, you could also use async-await here:
router.post("/validate", async (req, res) => {
try {
const validated_data = await validate_data(req.body.nottrusteddata)
const mydata = new Mydata({
id: req.body.id,
lst: validated_data
})
// Some useful stuff is here
res.status(200).json()
}
catch(err) {
// Handle error
}
})
I'm new to async/await.
I'm trying to use async and await but the query is not waiting and it happens at last and the page renders before the query so I can't get the correct answer on rendered page.
Here is my code before using async await
orderMiddleware.newOrder = function (req, res) {
var total = 0
var curr_total = 0
// get items from cart
c.query('select * from cart where user_id=:userId',
{ userId: req.user.ID }, function (err, cart) {
if (err) {
console.log(err)
} else {
cart.forEach(function (item) {
// Find item from DB and check their price
c.query('select * from products where id=:id',
{ id: item.item_id },
function (err, foundItem) {
if (err) {
console.log(err)
} else {
curr_total = foundItem[0].price * item.quantity
console.log("currenttotal" + curr_total)
total += curr_total
console.log(total)
}
})
})
console.log(total)
console.log(curr_total)
// Calculate total price
// Multiply all items with their quantity
res.render('orders/new', { cart: cart, total: total })
}
})
}
However this doesn't work properly. console.log(total) happens before the query so the result is zero and it renders zero in the rendered page.
Same thing happens if I use async. Am I using it wrong?
After using async await-
orderMiddleware.newOrder = async (req, res) => {
var total = 0
var curr_total = 0
// get items from cart
var A= c.query('select * from cart where user_id=:userId',
{ userId: req.user.ID }, async (err, cart) => {
if (err) {
console.log(err)
} else {
cart.forEach(async (item) => {
// Find item from DB and check their price
await c.query('select * from products where id=:id',
{ id: item.item_id },
async (err, foundItem) =>{
if (err) {
console.log(err)
} else {
curr_total = foundItem[0].price * item.quantity
console.log("currenttotal" + curr_total)
total += curr_total
console.log(total)
}
})
})
await console.log(total)
// await console.log(curr_total)
// Calculate total price
// Multiply all items with their quantity
await res.render('orders/new', { cart: cart, total: total })
}
})
}
I tried without using callbacks like:
var A= c.query('select * from cart where user_id=:userId',
{ userId: req.user.ID })
but then how can I get the output of the query?
console.log(A) shows different results.
You can't because the functions don't return promises. You can promisify those function using a thirty-part library (for example es6-promisify) or you can wrap those by yourself.
Once a function returns a Promise, you can await it.
For example, for the above, a solution could be the following:
const execQuery = (sql, params) => new Promise((resolve, reject) => {
query(sql, params, (error, data) => {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
const logCartItem = async (userId) => {
try {
const items = await execQuery('select * from cart where user_id=:userId', { userId });
items.forEach(console.log);
} catch (error) {
console.error(error);
}
};
Assuming you're using the node-mariasql package. Short answer is you can't use async/await because the package does not support Promises.
With node-mariasql it's easy to use promisify
const util = require('util')
const asyncQuery = util.promisify(c.query);
const rows = await asyncQuery.call(c, 'SELECT product FROM products WHERE id = :id', { id }, { useArray: false, metaData: false })