How to pass into Seq reponse of function? - javascript

I want to pass into Seq([644511,340755]) an response from async function getProjects.
So I tried
...
var ids = pivotal.getProjects(function (err, data) {
var ids = data.project.map(function(x) { return parseInt(x.id); });
console.log("IDS_i: ".red + ids);
});
console.log("IDS_e: ".red + ids);
Seq(ids)
.parEach(function(project_id) {
....
Logs:
IDS_e: undefined
GET /stories 200 34ms
GET /favicon.ico 404 2ms
IDS_i: 644511,340755
I am wondering maybe I should put this into Seq:
Seq()
.seq(function() {
pivotal.getProjects(function (err, data) {
data.project.map(function(x) { return parseInt(x.id); });
});
}
but how to return ids as array in that case?

getProjects is also async. A basic rule: You can't return any values from an async function. You have to do all processing in the callback function. Execution will continue before your arrays have been aggregated. So your seq approach is what you need:
Seq()
.seq(function() {
pivotal.getProjects(this);
})
.flatten()
.seqEach(function(project) {
var projectId = project.id;
myService.someOtherAsyncAction(projectId, this);
});
node-seq will take care of passing the result of the callback to the next seq step by passing this as the callback function to your async function. This is how flow and results are passed to the next step. flatten will make sure each project is available as individual elements on the stack so you can do seqEach in the next step.

Related

Returning results with callbacks

I'm trying to query a database, then make an array of objects from the results, turn them into a JSON object.
I'm not used to Javascript's asynchronous nature and I'm really confused about how to implement something that needs to work synchronously. I know I probably need to use callbacks, but after looking at many tutorials I'm only left more confused.
This is the code without callbacks:
var foreignTable = (tablename,idArr)=>{
var dataArray = [];
//call a query for each of the ids
var objectToAdd;
for(var id of idArr){
objectToAdd = queryForeignTable(tablename,id);
dataArray.push(objectToAdd);
}
return dataArray;
connection.end();
};
var queryForeignTable = (tablename,id)=>{
connection.query("SELECT * FROM "+tablename+" WHERE id="+id, function (error, results, fields) {
if(error)throw error;
var objectToAddToArray={};
//Go through each field in a result and make the object
for(packet of fields){
var label = packet.name;
objectToAddToArray[label] = results[0][label];
}
return objectToAddToArray;
});
};
var arrayOfDrivers = foreignTable("driver",[1,2]);
outputJson["drive"]=arrayOfDrivers;
console.log(outputJson); // { drive: [ undefined, undefined ] }
I attempted foreignTable(tablename, idArr, callback) with the callback calling queryForeignTable with no luck.
Can someone explain how I can get this piece of code working with callbacks?
A callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action.
from MDN: Callback function
Callbacks are a way of telling a function what to do next, as in "after you're done, run this function".
For example:
first = function (callback) {
console.log("First!");
callback();
}
second = function () {
console.log("Second!");
}
first(second);
Will produce:
First!
Second!
You can also use anonymous function to product the same result:
first(function() {
console.log("Second!")
});
Regarding the specific example from your question, you are correct that you need to use callbacks a bit differently. Rather than using a return statement in each of your two functions, you'll need to use a callback. connection.query is asynchronously coming back with your results as results, but you can't return them to your queryForeignTable function. Instead, give queryForeignTable a callback function to run. The same idea goes for your foreignTable function.
I'm not connected to your database, obviously, so I stubbed a DB connection and simplified what you're trying to do, but it should look something like this:
// Stubbed DB connection
var connection = {};
connection.query = (id, cb) => {
var results = [
{
id: id,
name: 'Name of ' + id,
},
];
cb(null, results);
};
var foreignTable = (ids, cb) => {
var data = [];
for (var i = 0; i < ids.length; i++) {
queryForeignTable(ids[i], (error, obj) => {
data.push(obj);
if (i == ids.length - 1) {
cb(null, data);
}
});
}
};
var queryForeignTable = (id, cb) => {
connection.query(id, (error, results) => {
if (error) {
cb(error, null);
}
cb(null, results[0]);
});
};
foreignTable([1, 2], (error, data) => {
if (error) {
console.error(error);
}
console.log(data);
});
That produces:
[ { id: 1, name: 'Name of 1' }, { id: 2, name: 'Name of 2' } ]
In essence, when you have an urge to return some value(s) from a function in an asynchronous way, give the function a callback parameter, then invoke that callback with your return values.
You can run the code here: https://repl.it/K0YI/3
When you have an asynchronous call, like connection.query(statement, callback), then whatever you want to do with the results of that call needs to be done within the callback.
Bear in mind that async functions don't return the final value that you generally want (usually they return undefined). Instead of using a return value, you pass a callback as a way of saying, "when you're finished, carry on and do this with the results", aka. continuation-passing style.
One of the challenges in your code is that you're issuing separate queries for each ID, and then aggregating the results into an array of responses. This means you'll need to check when all the queries have completed, and only then proceed to display the final results.
Here's your example, re-written, commented, and simplified. Hopefully this helps to explain how the control flow works.
// Read row IDs 1 and 2 from the "driver" table.
readTable("driver", [1, 2], displayData);
// Print the results.
function displayData (arrayOfDrivers) {
console.log(arrayOfDrivers);
}
// Read all rows matching IDs in `idArray` from `tableName`,
// put results into an array, and finally invoke `callback`.
function readTable (tablename, idArray, callback) {
var resultsArray = [];
// Queue up all the async queries.
for (var id of idArray){
queryTable(table, id, handleResponse);
}
// A query finished, so handle the result.
function handleResponse (error, results, fields) {
if (error) {
throw error;
}
// Add the query result to array of results.
resultsArray.push(results[0]);
// Check if all queries are done.
if (resultsArray.length === idArray.length) {
// Invoke the callback with the resultsArray.
// The callback is in fact the `displayData` function.
callback(resultsArray);
}
}
}
// Execute a query, using the `cb` callback to handle the response.
function queryForeignTable (tablename, id, cb) {
var query = "SELECT * FROM " + tablename + " WHERE id=" + id;
connection.query(query, cb);
}
Note that the handleResponse function is defined within the scope of the readTable function, so it can access the variables in readTables scope, such as resultsArray and callback.
Hope that helps.

jquery post call function n times but wait for each to complete before calling next one

I have a list things that I'm sending to PHP one at a time via $.post. I want to wait for each to complete before calling the next. I want to do this with JS not doing the looping with PHP as I want the return value from each to display.
var list = ["a", "b", "c"];
for (i = 0; i < list.length; i++) {
$.post(con, {
callScript: list[i],
}, function(data, status) {
//Do stuff here with the data on success
});
}
I have looked at $.when but just can't sort out how to use it. Ever example assumes that there is a set number of functions and not the same function n times. I also know that async false is not allowed.
Is there a way to get that to run?
Recursion is your good friend here. You can create a function that invokes itself for each item, calling the next one after the async operation of the current one is finished. The recursion stops when we run out of items.
function postInOrder(list, index = 0) {
if (index < list.length) {
$.post(con, {
callScript: list[index],
}, function success(data, status) {
// do stuff here with the data on success
// ...
postInOrder(list, index + 1); // <-- run next script on completion
});
}
}
postInOrder(["a", "b", "c"])
Here's an example with a fake post method:
function postInOrder(list, index = 0) {
if (index < list.length) {
console.log(`Start: ${list[index]}`)
post(function success() {
console.log(`Finish: ${list[index]}`)
postInOrder(list, index + 1); // <-- run next script on completion
});
}
}
postInOrder(["a", "b", "c"])
function post(cb) { setTimeout(cb, 1000); }
You may also reduce it to a promise queue:
list.reduce(function(prom,listEl){
return prom.then(function(){
return new Promise(function(next){
$.post(con, {
callScript: listEl,
}, function(data, status) {
//Do stuff here with the data on success
next();
});
});
});
},Promise.resolve());
(I think wrapping into a promise is not neccessary, may someone whos shure about jquerys syntax feels free to edit ;))
There is a VERY widely used library called "async" that makes this sort of thing very easy. Here are the docs for async series which is probably what you want to do here.
Here is the example from the docs:
async.series([
function(callback) {
// do some stuff ...
callback(null, 'one');
},
function(callback) {
// do some more stuff ...
callback(null, 'two');
}
],
// optional callback
function(err, results) {
// results is now equal to ['one', 'two']
});
The pattern for the callback is pass an error as the first parameter if something went wrong, otherwise pass a null as the first parameter then the actual return value as the second parameter. That pattern is used all over asynchronous JavaScript on the server and in the browser.
That final function is what gets executed once the series is complete.
You can also get tricky with this knowing that variables can be functions in JavaScript and build your queue like this:
var processMe = [];
processMe.push(callScript('two'));
processMe.push(callScript('four'));
processMe.push(callScript('six'));
async.series([ processMe ],
function(err, results) {
// results is now equal to ['two', 'four', 'six']
});
function callScript(value, callback) {
// do something here
callback(null, value);
}
You can also use waterfall if you need to pass results from one step to another.
If it truly is the same code multiple times, use the times operator to simply iterate the same function N times:
// Pretend this is some complicated async factory
var createUser = function(id, callback) {
callback(null, {
id: 'user' + id
});
};
// generate 5 users
async.times(5, function(n, next) {
createUser(n, function(err, user) {
next(err, user);
});
}, function(err, users) {
// we should now have 5 users
});

Populating async array with a function called right before.

var request = require('request'),
requests = [],
values = [],
request("url1", function());
function() {
.....
for (x in list){
requests.push(requestFunction(x));
}
}
requestFunction(x){
request("url2", function (e,r,b) {
....
return function(callback) {
values[i] = b
}
});
}
async.parallel(requests, function (allResults) {
// values array is ready at this point
// the data should also be available in the allResults array
console.log(values);
});
I new to node. Issue is that the request needs to be called to populate the requests callback array. But the issue is the async.parallel will run before the requests array is full and need run all the callbacks. Where do I move this async so it runs after the requests array is full?
Asynchronous programming is all about chaining blocks. This allows node to efficiently run its event queue, while ensuring that your steps are done in order. For example, here's a query from a web app I wrote:
app.get("/admin", isLoggedIn, isVerified, isAdmin, function (req, res) {
User.count({}, function (err, users) {
if (err) throw err;
User.count({"verified.isVerified" : true}, function (err2, verifiedUsers) {
if (err2) throw err2;
Course.count({}, function (err3, courses) {
// and this continues on and on — the admin page
// has a lot of information on all the documents in the database
})
})
})
})
Notice how I chained function calls inside of one another. Course.count({}, ...) could only be called once User.count({"verified.isVerified" : true}, ...) was called. This means the i/o is never blocked and the /admin page is never rendered without the required information.
You didn't really give enough information regarding your problem (so there might be a better way to fix it), but I think you could, for now, do this:
var request = require('request'),
requests = [],
values = [],
length; // a counter to store the number of times to run the loop
request("url1", function() {
length = Object.keys(list).length;
// referring to the list below;
// make sure list is available at this level of scope
for (var x in list){
requests.push(requestFunction(x));
length--;
if (length == 0) {
async.parallel(requests, function (allResults) {
console.log(values); // prints the values array
});
}
}
}
function requestFunction(x) {
request("url2", function (e,r,b) {
values[i] = b;
return b;
}
}
I am assuming that requestFunction() takes a while to load, which is why async.parallel is running before the for (var x in list) loop finishes. To force async.parallel to run after the loop finishes, you'll need a counter.
var length = Object.keys(list).length;
This returns the number of keys in the list associative array (aka object). Now, every time you run through the for loop, you decrement length. When length == 0, you then run your async.parallel process.
edit: You could also write the requests.push() part as:
requests.push(
(function () {
request("url2", function (e,r,b) {
values[i] = b;
return b;
}
})()
);
I think it's redundant to store b in both values and requests, but I have kept it as you had it.

NodeJS async callback. How to return the list, from a async callback?

So basically I am making a database query, to get all posts with a certain id, then add them to a list, so I can return. But the list is returned, before the callback has finished.
How do I prevent it from being returned before callback has finished?
exports.getBlogEntries = function(opid) {
var list12 =[];
Entry.find({'opid' : opid}, function(err, entries) {
if(!err) {
console.log("adding");
entries.forEach( function(currentEntry){
list12.push(currentEntry);
});
}
else {
console.log("EEEERROOR");
}
//else {console.log("err");}
});
console.log(list12);
return list12;
};
ALL callback is asynchronous, so we don't have any guarantee if they will run exactly in the order we have leave them.
To fix it and make the process "synchronous" and guarantee an order executation you have two solutions:
First: make all process in nested list:
instead of this:
MyModel1.find({}, function(err, docsModel1) {
callback(err, docsModel1);
});
MyModel2.find({}, function(err, docsModel2) {
callback(err, docsModel2);
});
use this:
MyModel1.find({}, function(err, docsModel1) {
MyModel2.find({}, function(err, docsModel2) {
callback(err, docsModel1, docsModel2);
});
});
The last snippet above guarantee us that MyModel2 will be executed AFTER MyModel1 is executed.
Second: Use some framework as Async. This framework is awesome and have several helper functions to execute code in series, parallels, whatever way we want.
Example:
async.series(
{
function1 : function(callback) {
//your first code here
//...
callback(null, 'some result here');
},
function2 : function(callback) {
//your second code here (called only after the first one)
callback(null, 'another result here');
}
},
function(err, results) {
//capture the results from function1 and function2
//if function1 raise some error, function2 will not be called.
results.function1; // 'some result here'
results.function2; // 'another result here'
//do something else...
}
);
You could use sync database calls but that would work around the concept of node.js.
The proper way is to pass a callback to the function that queries the database and then call the provided callback inside the database query callback.
How do I prevent it from being returned before callback has finished?
The callback is asynchronous, and you cannot avoid that. Hence, you must not return a list.
Instead, offer a callback for when it's filled. Or return a Promise for the list. Example:
exports.getBlogEntries = function(opid, callback) {
Entry.find({'opid': opid}, callback); // yes, that's it.
// Everything else was boilerplate code
};
There is an alternate way to handle this scenario. You can use the async module and when the forEach has finished then make the return call. Please find the code snippet below for the same:
var async = requires('async');
exports.getBlogEntries = function(opid) {
var list12 =[];
Entry.find({'opid' : opid}, function(err, entries) {
if(!err) {
console.log("adding");
async.forEachSeries(entries,function(entry,returnFunction){
list12.push(entry);
},function(){
console.log(list12);
return list12;
});
}
else{
console.log("EEEERROOR");
}
});
};

Async execution of redis commands

I'm trying to execute several async methods of redis with the following code
var redis = require("redis");
var client = redis.createClient();
var async = require("asyncjs");
async.list([
client.hincrby("traffic:" + siteId, 'x', 1),
client.hincrby("traffic:" + siteId, 'y', 1),
client.hincrby("traffic:" + siteId, 'z', 1)
]).call().end(function(err, result)
{
console.log(err); // returns error [TypeError: Object true has no method 'apply']
console.log(result); // undefined
if(err) return false;
return result;
});
All the methods execute successfully
but i get the error [TypeError: Object true has no method 'apply']
the method is executed and returns true, and its probably interpreting that as true, but i don't see why it has to use the method apply on it?
I can get the result of the increment by adding a function(err, result) to client.hincrby as last element.. but how do i get all the results in the result variable in the end function?
I suppose the asyncjs module you use is the one documented at:
https://github.com/fjakobs/async.js
In your code:
list() is a generator. It allows the array to be iterated by asyncjs. The array is an array of values.
call() is a mapper which calls each item. The items has therefore to be callable (i.e. they have to be callbacks).
end() is a termination end point, called when the iteration is over. As a parameter, you only get the last value of the sequence (not the whole sequence).
You got the "[TypeError: Object true has no method 'apply']" error because the list you have built is not a list of callbacks. It is a list of values.
Here is some code which should do what you want:
var redis = require("redis");
var client = redis.createClient();
var async = require("asyncjs");
function main() {
var siteId = 1;
async
.list(['x','y','z'])
.map( function (item,next) {
client.hincrby('traffic:' + siteId, item, 1, function (err,res) {
next(err,res)
})
})
.toArray( function(err,res) {
console.log(err);
console.log(res);
});
}
main()
Please note here we use map() instead of call(), and toArray() instead of end().

Categories