I have inherited an application which relies entirely on async.waterfall for one of its controllers. I don't want to re-write the whole thing, but in fixing a couple items want to convert the callback param into a more generic handler.
Right now an example function looks like the below (same pattern exists for like a dozen more functions)
function create(data, callbackParent) {
async.waterfall(
[
function(callback) {
if (!interface) {
return init(callback);
} else {
return callback(null);
}
},
function(callback) {
return interface.create(data, callback);
}
],
(error, result) => {
if (error) {
const codeIndex = error.indexOf('statusCode=') + 'statusCode='.length;
const errorStatusCode = error.slice(codeIndex, codeIndex + 3);
if(errorStatusCode == 401) {
reconnect(callbackParent);
}
else {
return callbackParent(error);
}
}
return callbackParent(null, result);
}
);
}
No problem and works great; however, notice the callback there. I am trying to move that into some generic function handleResponse(error, result) for example:
function create(data, callbackParent) {
async.waterfall(
[
function(callback) {
if (!interface) {
return init(callback);
} else {
return callback(null);
}
},
function(callback) {
return interface.create(data, callback);
}
],
(error, result) => handleResponse
);
}
I've tried various combos of syntax like the above but either everything blows up, or I lose context to my callbackParent.
How can I construct the callback for an async.waterfall array to be a separate and generic handler?
Related
I have a question about asynchronous function. Here my function "My_function":
function My_function (my_name, callback){
stmt = db.prepare ("SELECT number_table1 from my_table1 WHERE user=?");
stmt.bind(my_name);
stmt.get(function(error,row){
if(error){
throw err;
}
else{
if(row){
callback(number_table1);
}
else{
console.log("error");
}
}
});
}
Work fine but I have 2 tables and I need do other query and I need add two numbers so... in my function I need do too this query:
stmt = db.prepare ("SELECT number_table2 from my_table2 WHERE user=?");
and finally return back in my callback "number_table1 + number_table2".
Somebody know how to solve it? Thanks in advance.
Best regards!
In cases like your's I like to use the async module, because the code will be more legible. Example:
var async = require('async');
function My_function(my_name, callback) {
var stmt = db.prepare("SELECT number_table1 from my_table1 WHERE user=?");
stmt.bind(my_name);
stmt.get(function (error, row) {
if (error) {
callback(error, null);
} else {
if (row) {
callback(null, number_table1);
} else {
callback(new Error("row not found"), null);
}
}
});
}
//if you need the results in a specific order, you can use "series" instead of "parallel".
async.parallel(
//array of functions
[
function (callback) {
My_function('firstName', callback);
},
function (callback) {
My_function('secondName', callback);
}
],
//results when all functions ends
//the results array will equal [number_table1, number_table2], but the order can be different, because of the parallelism
function (err, results) {
if (err) {
//handle the err
} else {
//do something
}
}
);
Docs:
http://caolan.github.io/async/docs.html#parallel or
http://caolan.github.io/async/docs.html#series
You need to synchronize the functions so that you can be sure both their results are ready before calling back. You can do this using promises: https://www.promisejs.org/
Make two regular functions (no callbacks), one for each query (function1, function2)
Make both return a promise
Then you can do
function My_function(my_name) {
var value1;
function1(my_name)
.then(function(resultFromFunction1) {
value1 = resultFromFunction1;
return function2(my_name);
})
.then(function(resultFromFunction2) {
var result = value1 + resultFromFunction2;
return result;
});
}
}
Make sure to catch errors and handle different outcomes, what I presented is its simplest form.
Update
Here is an example of a function doing a query and returning a promise
function1 = function(user) {
return new Promise(function (resolve, reject) {
pool.getConnection(function (err, connection) {
if(err) {
reject ({status : false, message : "Error in connection database"});
} else {
connection.query('SELECT number_table1 from my_table1 WHERE user=?', [user], function(err, rows){
connection.release();
if(!err) {
resolve ({status: true, message: rows});
} else {
reject ({status: false, message: err});
}
});
}
});
});
}
Make the table names function parameters. Convert that function to use async/await or promise. Use Promise.all to run both queries.
In my function i have to call async series inside async foreach to compute the final result and create my json.is that possible
async.series([
function(callback) {
});
},
function(callback) {
async.forEachSeries(temp,function(quest,callback) {
}, function(err) {
if (err) return next(err);
});
callback();
}
],
function(err) {
if (err) return next(err);
res.json(output);
});
You should be able to nest as much async functions into each other, however it is better to use a naming conventions so you can easily track which callbacks are passed where and to avoid collisions due to hoisting. So basically this should work as you'd expect:
async.series([
function first(seriesCallback) {
seriesCallback();
},
// other functions in series
// ...
function (seriesCallback) {
var someArray = [];
async.each(someArray, function (value, eachCallback) {
// process the value
// return an error if there is one
eachCallback(err);
}, function(err) {
// add any additional processing you might need
// pass the control to the parent async method and handle the errors
// in a more central place if there is an error here it will
// be processed in onSeriesDone as well as all other errors
seriesCallback(err);
});
}], function onSeriesDone(err) {
next(err);
});
I don't understand how to call a function recursively in node.js for example:
var releaseStock = function (callback) {
getItems(function (err, items) {
if (err) {
return callback(err);
} else {
if (items) {
return callback(items);
} else {
setTimeout(function() {
releaseStock(callback);
}, 5000);
}
}
});
};
How can i make it work?
I'm not entirely sure what you want to do, but I suspect it is something along the lines of:
var releaseStock = function(callback) {
// get items from somewhere:
var items = getItems();
if (!items) {
// if there are no items, try again (recurse!):
return releaseStock(callback);
}
// if there are items, give them to the callback function:
return callback(items);
};
I'd like to perform queries in order but I don't know what is the best approach.
Let's say I'd like to do the following :
if (name) {
//first query
db.query({name:name}).exec(function(err,result) {
//save result
})
}
if (isEmpty(result)) {
//make another query
db.query({anotherField:value}).exec(function(err,result) {
//save result
})
}
Should I use promises on that case?
That would be an example with cakePHP :
if (!isset($field1)) {
$result = $this->item->find( ... conditions => ... = $field2);
} else {
if (!isset($field2)) {
$result = $this->item->find( ... conditions => ... = $field1);
} else {
$result = $this->item->find( ... conditions => ... = $field1 && ... =$field2);
if (empty($result)) {
$result = $this->item->find( ... conditions => ... =$field2);
}
}
}
If you mean "in order" you can nest the callbacks. Passing callbacks is the classic (non-promise) way to structure asynchronous code:
function doMultipleAsyncThings(name, callback){
if (name) {
//first query
db.query({name:name}).exec(function(err,result) {
if (isEmpty(result)) {
//make another query
db.query({anotherField:value}).exec(function(err,result) {
//save result
})
} else {
//save result
}
})
} else {
return callback('no name');
}
}
Heads up, after more than 2 or so operations, you end up in 'callback hell' with 100+ lines of nested code, the async library is helpful for this:
var async = require('async');
doMultipleAsyncThings('plato', function(){
console.log(arguments)
});
function doMultipleAsyncThings(name, callback){
// `callback` is a passed-in function to call after doMultipleAsyncThings is done
// Here, it is the function we passed in above after 'plato'
async.waterfall([function(done){
done(null, name);
},
firstQuery,
secondQuery,
], callback)
}
function firstQuery(name, done){
if (name) {
// You can define and pass a callback inline:
db.query({name:name}).exec(function(err,result) {
done(err, result);
})
} else {
done('no name');
}
}
function secondQuery(result, done){
if (isEmpty(result)) {
// You can pass a callback by reference:
db.query({anotherField:value}).exec(done)
} else {
//save result
done();
}
}
Promises would be a good fit for this, the q library is the most common for this. You probably just want to nest your promises like so:
var q = require('q');
if (name) {
//first query
q.ninvoke(db.query({name:name}), 'exec')
.then(function(result) {
//save result
if (isEmpty(result)) {
//make another query
q.ninvoke(db.query({anotherField:value}), 'exec')
.then(function(result) {
//save result
})
.fail(function(err) {
//handle failure
console.error(err, err.stack);
});
}
})
.fail(function(err) {
//handle failure
console.error(err, err.stack);
});
}
q.ninvoke allows us to convert standard nodejs functions into their promise equivalent.
I'm getting a 'callback already used' error and I don't know why. I am using async and want to chain two functions because the second function depends on the first function to complete.
I'm new-ish to Node.js and still wrapping my head around async/callbacks. Thanks so much for helping out.
getCdn takes in cnames, and if the cname is part of a CDN it pushes the results into a global variable called cdnAttrs.
function getCdn(cnameDict, callback) {
// cdnAttributes contains associative array with each web attribute: {name_in_db : code_snippet_to_find_in_cname}
for (var key in cdnAttributes) {
if (cdnAttributes.hasOwnProperty(key)) {
var snippet = -1;
// some technologies contain multiple code snippets, in that case they are stored as array. Single code snippets are stored as string
if (!Array.isArray(cdnAttributes[key])) {
snippet = cnameDict['cname'].indexOf(cdnAttributes[key])
}
else {
// check each code snippet within the array, if any match the source code, update 'snippet'
for (var n = 0; n < cdnAttributes[key].length; n++) {
var val = cnameDict['cname'].indexOf(cdnAttributes[key][n])
if (val > -1) {
snippet = val
}
}
}
// if attribute found in tag, create cdnAttrs[cdn] = [{from: hostname, proof: cname}, {from: hostname2, proof: cname2}, ...]
if (snippet > -1) {
try {
cdnAttrs[key].push(cnameDict);
}
catch (e) {
cdnAttrs[key] = [];
cdnAttrs[key].push(cnameDict);
}
callback();
} else {
callback();
}
} else {
callback();
}
}
}
My async function looks like this:
async.series([
// THIS FUNCTION WORKS FINE...
function(callback) {
async.each(toCheck, function(hostname, callback) {
getCname(hostname, callback);
},callback);
},
// THIS FUNCTION RETURNS RETURNS Error("Callback was already called.")
function(callback) {
async.each(toCheckCnames, function(cnameDict, callback) {
getCdn(cnameDict, callback);
},callback);
}
], function(err){
if(err) {
console.log('ERROR');
}else{
console.log('toCheckCnames is done: '+JSON.stringify(toCheckCnames));
console.log('cdnAttrs is done: '+JSON.stringify(cdnAttrs));
}
})
the getCnames function works:
function getCname(hostname, callback){
dns.resolve(hostname, 'CNAME', function(error, cname) {
if (cname) {
toCheckCnames.push({from: hostname, cname: cname[0]});
callback();
}
// if not CNAMEd, check SOA on www.domain.com and domain.com
else {
if (hostname.slice(0,4) == 'www.') {
hostname = hostname.slice(4);
}
nativedns.resolve(hostname, 'SOA', function(error, records) {
if(!error && records) {
toCheckCnames.push({from: hostname, cname: records[0]['primary']});
callback();
}
else if (!error) {
hostname = 'www.'+ hostname
nativedns.resolve(hostname, 'SOA', function(error, records) {
if (!error) {
toCheckCnames.push({from: hostname, cname: records[0]['primary']});
callback();
}
else callback()
});
}
else callback()
});
}
});
}
Your getCdn function is a loop that will call the callback after each iteration. If calling the callback is intended to stop the loop execution, you can do return callback(). Otherwise you need to reorganize your code to only call the callback once when the function is done.
UPDATE:
You can also simplify your async.each calls:
// Was this
async.each(toCheck, function(hostname, callback) {
getCname(hostname, callback);
},callback);
// Could be this
async.each(toCheck, getCname, callback);