Async is calling callback before last item? - node.js

I have an async.eachSeries() function which process a list of objects. After the list I want a res.send() function so I can send the result back to the frontend.
But I'm getting a
'Can't set headers after they are sent'
error on the res.send() line, which it's looks that the function is called before the list is completely processed.
module.exports.createOrder = function(req,res){
console.log("CreateOrder");
var orderDescription = "";
var payId = new ObjectId(); //create one id, to pay multiple orders at one time
var shopList = groupByShop(req.body.cart);
var orders = [];
var result = {};
console.log("before async");
//the cart is now sorted by shop, now we can make orders for each shop
async.eachSeries(Object.keys(shopList), function(key, callback){
console.log("in async");
var shop = shopList[key];
console.log("before saveOrder");
saveOrder(payId, shop, key, req.body, req.user, function(err, newOrder){
console.log("in saveorder");
if(err){
console.log("Err", err);
callback(err);
}else{
console.log("order saved");
orders.push(newOrder);
callback();
}
})
}, function(err){
if(err){
console.log("One or more orders are not saved:", err);
return res.status(400).json(err);
}else{
console.log("All orders are processed");
result = {
message: 'OK',
order: {
payId: orders[0].payId
}
};
return res.send(200).json(result);
}
})
}
What is going wrong here? Currently testing with one object in the 'shopList', and all log lines are visible in the server console.
When I remove the line, the function is working fine, but, of course, he is not sending any results. I also tried to move the line outside the function, but that cause, of course again, in a empty result{} and a sending before the function is done.

res.send(200) will send a HTML response with content '200' - what you want to do is res.status(200).json(result) although res.json(result) should also work fine.

Related

How To Bind Node-js DB Query to Web Form

I'm using node and postgres, I'm new to writing async function, what I'm trying to do is a very simple query that will do a total count of records in the database, add one to it and return the result. The result will be visible before the DOM is generated. I don't know how to do this, since async function doesn't return value to callers (also probably I still have the synchronous mindset). Here's the function:
function generateRTA(callback){
var current_year = new Date().getFullYear();
const qry = `SELECT COUNT(date_part('year', updated_on))
FROM recruitment_process
WHERE date_part('year', updated_on) = $1;`
const value = [current_year]
pool.query(qry, value, (err, res) => {
if (err) {
console.log(err.stack)
} else {
var count = parseInt(res.rows[0].count) + 1
var rta_no = String(current_year) + '-' + count
callback(null, rta_no)
}
})
}
For the front-end I'm using pug with simple HTML form.
const rta_no = generateRTA(function (err, res){
if(err){
console.log(err)
}
else{
console.log(res)
}
})
app.get('/new_application', function(req, res){
res.render('new_application', {rta_number: rta_no})
});
I can see the rta_no in console.log but how do I pass it back to the DOM when the value is ready?
Based on the ajax call async response, it will update the div id "div1" when it gets the response from the Node js .
app.js
app.get("/webform", (req, res) => {
res.render("webform", {
title: "Render Web Form"
});
});
app.get("/new_application", (req, res) => {
// Connect to database.
var connection = getMySQLConnection();
connection.connect();
// Do the query to get data.
connection.query('SELECT count(1) as cnt FROM test ', function(err, rows, fields) {
var person;
if (err) {
res.status(500).json({"status_code": 500,"status_message": "internal server error"});
} else {
// Check if the result is found or not
if(rows.length==1) {
res.status(200).json({"count": rows[0].cnt});
} else {
// render not found page
res.status(404).json({"status_code":404, "status_message": "Not found"});
}
}
});
// Close connection
connection.end();
});
webform.pug - Via asynchronous call
html
head
script(src='https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js')
script.
$(document).ready(function(){
$.ajax({url: "/new_application", success: function(result){
$("#div1").html(result.count);
}});
});
body
div
Total count goes here :
#div1
value loading ...
That seems okay, I'm just not sure of this:
The result will be visible before the DOM is generated
This constraint defeats the purpose of async, as your DOM should wait for the returned value to be there. Instead of waiting for it you could just render the page and once the function returns and runs your callback update the value.
Also, perhaps it's worth having a look into promises

Not reaching aync callback, response hangs

I've run into an issue and I'm unsure what steps to take to fix it. Right now all the data is being correctly retrieved however the responseCallback is never firing and thus i'm not reaching the res.json call with the response array. Any guidance here would be greatly appreciated! Thanks.
For clarification the problem is with the aysnc.each callback.
var updatedBusinesses = [];
googleplaces.radarSearch({location:"lat,long",radius:"10000",keyword:"keywordhere"},function(err,response){
if(err){return next(err);}
async.each(response.results,function(currResponse,responseCallback){
Business.findOne({"placesId":currResponse.place_id,"claimed":true}).populate({path:'services',select:''}).exec(function(err,business){
if(err){return next(err);}
if(business !== null){
Service.populate(business.services,{path:'employees',select:'_id appointments firstName lastName username avatarVersion'},function(err,newBusiness){
if(err){return next(err);}
googleplaces.placeDetailsRequest({placeid:business.placesId},function(error,placesResult){
if(error){return responseCallback(error);}
console.log("RESULT OF THE GOOGLE PLACES DETAIL SEARCH")
placesResult.result.info = business;
updatedBusinesses.push(placesResult.result);
// Here the data is populated and correct.
// console.log(updatedBusinesses)
responseCallback();
});
})
}
})
},function(err){
if(err){return next(err);}
console.log("called")
res.json(updatedBusinesses);
})
})
This is where I'm hoping to return the updated business information to the client however this never fires.
},function(err){
if(err){return next(err);}
console.log("called")
res.json(updatedBusinesses);
})
})
async.each() expects a callback (responseCallback) to be called for every iteration. If it's not called, it's sitting there waiting for it. That's why your update business section never gets called.
Inside your async.each(), there are a number of places calling next() which is not async.each()'s iteration's callback (responseCallback). Here is the revised code that calls the callbacks properly:
var updatedBusinesses = [];
googleplaces.radarSearch({location:"lat,long",radius:"10000",keyword:"keywordhere"},function(err,response){
if(err){return next(err);}
async.each(response.results,function(currResponse,responseCallback){
Business.findOne({"placesId":currResponse.place_id,"claimed":true}).populate({path:'services',select:''}).exec(function(err,business){
if(err){
return responseCallback(err);// <== calling responseCallback instead of next()
}
// in case of business === null/undefined, I'm not seeing any
// callback getting called, it needs to be called inside
// async.each() no matter which condition it is
if (!business) {
// call responseCallback to continue on with async.each()
return responseCallback();
}
Service.populate(business.services,{path:'employees',select:'_id appointments firstName lastName username avatarVersion'},function(err,newBusiness){
if(err){
return responseCallback(err);// <== calling responseCallback instead of next()
}
googleplaces.placeDetailsRequest({placeid:business.placesId},function(error,placesResult){
if(error){return responseCallback(error);}
console.log("RESULT OF THE GOOGLE PLACES DETAIL SEARCH")
placesResult.result.info = business;
updatedBusinesses.push(placesResult.result);
// Here the data is populated and correct.
// console.log(updatedBusinesses)
responseCallback();
});
})
})
},function(err){
if(err){return next(err);}
console.log("called");
res.json(updatedBusinesses);
});
});
So now responseCallback() is called for every condition inside async.each(). It should get down to "updated business information" part of the code now.
Changing your code responseCallback(); with responseCallback(null, [your-result]); I think should do the job
var updatedBusinesses = []; googleplaces.radarSearch({location:"lat,long",radius:"10000",keyword:"keywordhere"},function(err,response){
if(err){return next(err);}
async.each(response.results,function(currResponse,responseCallback){ Business.findOne({"placesId":currResponse.place_id,"claimed":true}).populate({path:'services',select:''}).exec(function(err,business){
if(err){return next(err);}
if(business !== null){
Service.populate(business.services,{path:'employees',select:'_id appointments firstName lastName username avatarVersion'},function(err,newBusiness){
if(err){return next(err);}
googleplaces.placeDetailsRequest({placeid:business.placesId},function(error,placesResult){
if(error){return responseCallback(error);}
console.log("RESULT OF THE GOOGLE PLACES DETAIL SEARCH")
placesResult.result.info = business;
updatedBusinesses.push(placesResult.result);
// Here the data is populated and correct.
// console.log(updatedBusinesses)
responseCallback(null, updatedBusinesses);
});
})
} }) },function(err, updatedBusinesses){
if(err){return next(err);}
console.log("called")
res.json(updatedBusinesses);
})
})

Trying to implement callback, or some way to wait until my get request is done. This is in node.js express

Im trying to implement some way to stop my code to redirect me before I get the response from the omdb api I am using.
My function for making a search for a movie and saving all titles in a session looks like this:
app.post('/search', isLoggedIn, function(req, res) {
function getMovies(arg, callback){
console.log('In getMovies');
console.log('searching for '+arg);
omdb.search(arg, function(err, movies) {
if(err) {
return console.error(err);
}
if(movies.length < 1) {
return console.log('No movies were found!');
}
var titles = [];
movies.forEach(function(movie) {
// If title exists in array, dont push.
if(titles.indexOf(movie.title) > -1){
console.log('skipped duplicate title of '+movie.title);
}
else{
titles.push(movie.title);
console.log('pushed '+movie.title);
}
});
// Saves the titles in a session
req.session.titles = titles;
console.log(req.session.titles);
});
// Done with the API request
callback();
}
var title = req.body.title;
getMovies(title, function() {
console.log('Done with API request, redirecting to GET SEARCH');
res.redirect('/search');
});
});
However I dont know if I implement callback in the right way, because I think there can be a problem with the api request actually executing before the callback, but not finishing before. And therefor the callback is working..
So I just want 2 things from this question. Does my callback work? And what can I do if a callback won't solve this problem?
Thankful for all answers in the right direction.
Add
callback();
To, like this
omdb.search(arg, function(err, movies) {
if (err) {
return console.error(err);
}
if (movies.length < 1) {
return console.log('No movies were found!');
}
var titles = [];
movies.forEach(function(movie) {
// If title exists in array, dont push.
if (titles.indexOf(movie.title) > -1) {
console.log('skipped duplicate title of ' + movie.title);
} else {
titles.push(movie.title);
console.log('pushed ' + movie.title);
}
});
// Saves the titles in a session
req.session.titles = titles;
callback();
});
omdb.search is asynchronous function that's why callback executed before omdb.search

Mongoose with async queue & waterfall

I aim to import large amount of data by Mongoose. As a newbie, I fail to setup the flow control properly with various mechanisms by async. Glad if someone could point to an appropriate solution. Thanks.
var async = require('async'),
mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
var Cat = mongoose.model('Cat', { name: String });
// Imagine this is a huge array with a million items.
var content = ['aaa', 'bbb', 'ccc'];
var queries = [];
content.forEach(function(name) {
queries.push(function(cb) {
var obj = new Cat({ name: name });
obj.save(function(err) {
console.log("SAVED: " + name);
console.log(err);
});
return true;
});
});
// FAILED: async.parallel adds all content to db,
// but it would exhaust the resource with too many parallel tasks.
async.parallel(queries, function(err, result) {
if (err)
return console.log(err);
console.log(result);
});
// FAILED: save the first item but not the rest
async.waterfall(queries, function(err, result) {
if (err)
return console.log(err);
console.log(result);
});
// FAILED: same as async.waterfall, async.queue saves the first item only
var q = async.queue(function(name, cb) {
var obj = new Cat({ name: name });
obj.save(function(err) {
console.log("SAVED: " + name);
console.log(err);
});
})
q.push(content, function (err) {
console.log('finished processing queue');
});
I think eachLimit or eachSeries fit your situation best:
var content = ['aaa', 'bbb', 'ccc'];
async.eachLimit(content, 10, function(name, done) {
var obj = new Cat({ name : name });
obj.save(done);
// if you want to print some status info, use this instead:
//
// obj.save(function(err) {
// console.log("SAVED: " + name);
// console.log(err);
// done(err);
// });
//
}, function(err) {
// handle any errors;
});
With eachLimit, you can run an X amount of queries 'in parallel' (10 in the example above) to speed things up without exhausting resources. eachSeries will wait for the previous save before it continues with the next, so effectively saving one object at a time.
Notice that with each*, you won't get a list with (saved) objects back (it's a bit of a fire-and-forget mechanism where you're not interested in the outcome, bar any errors). If you do want a list of saved objects in the end, you can use the equivalent map* functions: mapLimit and mapSeries.

Getting meteor call to return stripe payments response

I've been working with Meteor and the stripe package to try and make a customer. So First I have my client side code which calls a method on the server so when clicked i have in the client.js:
Meteor.call('usersignup', function (error, result) {
console.log (result);
});
So this calls the Method on the server.js:
var Future = Npm.require('fibers/future');
var stripe = StripeAPI('my key'); // secret stripe API key
Meteor.methods({
usersignup: function(cusEmail){
var fut = new Future();
stripe.customers.create(
{ email: cusEmail },
function(err, customer) {
if (err) {
console.log(err);
fut.ret;
}
fut.ret(customer);
}
);
return fut.wait();
},
userfail: function(cusid){
var fut = new Future();
stripe.customers.retrieve(cusid, function(err, result) {
if(err){
console.log(err);
fut.ret;
}
fut.ret(err, result);
});
return fut.wait();
}
});
Now this works and creates a customer when I log onto the stripe.com dashboard but I'm trying to get the response returned to the client well at least the customer id for now and print it in the console. This is where I can't seem to get it to work. It'll log undefined when I do console.log(result). Any ideas?
EDIT: So I put the fiber and the stripe key as global variables now and don't get an error but the returns don't seem to be returning any values. so on the client side I have:
'click #signupsubmit': function (event) {
console.log("hello");
var whatis = getVal(); // function gets value of forms and returns object
var testid;
var cusid = Meteor.call('usersignup', whatis.email, function (error, result) {
if (error) {
console.log(err.message);
return;
}
console.log(result);
console.log("meteor call");
testid = result;
return (result);
});
console.log("outside call");
console.log(testid);
console.log(cusid);
},
});
So i've been running some console.log tests and it seems it executes the meteor.call and keeps going down the line. Console.log of both testid and cusid return undefined but a couple seconds later I receive the console.log of result and the string "meteor call" from inside the meteor.call. Is there a way to wait for the meteor call to finish then run the rest of what is in my click function? so console output will go like:
"hello"
"outside call"
test id undefined
cusid undefined
"meteor call"
"result"
Keep in mind that the stripe API doesn't use Fibers. You need to put it in manually. The callback doesn't reach the client because by then it would have already got a response (its async)
You can use something like this to wait for a result from the stripe callback before a result is returned to the client:
var stripe = StripeAPI('mykeygoeshere'); // secret stripe API key
var Future = Npm.require('fibers/future');
var fut = new Future();
stripe.customers.create(
{ email: 'hello#example.org' },
function(err, customer) {
if (err) {
console.log(err.message);
fut.ret;
}
fut.ret("customer id", customer.id);
}
);
return fut.wait();
Here a Future is used and it waits for a result to be received from the stripe callback before a result is returned to the client.
More info can be found on Fibers/Futures & Synchronous Callbacks incuding how to go about them & when to use them:
Meteor: Calling an asynchronous function inside a Meteor.method and returning the result
https://github.com/laverdet/node-fibers
https://gist.github.com/possibilities/3443021
Here's something simpler. Meteor now has Meteor.wrapAsync() for this kind of situation:
var stripe = StripeAPI("key");
Meteor.methods({
yourMethod: function(callArg) {
var charge = Meteor.wrapAsync(stripe.charges.create, stripe.charges);
charge({
amount: amount,
currency: "usd",
//I passed the stripe token in callArg
card: callArg.stripeToken,
}, function(err, charge) {
if (err && err.type === 'StripeCardError') {
// The card has been declined
throw new Meteor.Error("stripe-charge-error", err.message);
}
//Insert your 'on success' code here
});
}
});
I found this post really helpful:
Meteor: Proper use of Meteor.wrapAsync on server

Resources