Can't successfully pass through a callback into a function chain - javascript

I'm using the codebird library to perform some requests to the Twitter API. These requests respond correctly but I want to pass that response through to my route. Here is a snippet of my route.js:
router.get('/twitter', function(req, res) {
twitterAPI.callAPI(function(tweetData) {
console.log('callback for twitterCall');
res.send(tweetData);
});
});
This is my code from module1.js:
require('es6-promise').polyfill();
require('isomorphic-fetch');
var Codebird = require("codebird");
// using twitter-aggregation app for consumer key and secret
var params = {
screen_name: 'kanyewest'
};
var config = {
twitter : {
consumerKey: 'thisismykey',
consumerSecret: 'thisismysecret'
}
};
var cb = new Codebird;
cb.setConsumerKey(config.twitter.consumerKey, config.twitter.consumerSecret);
var tweetData = {};
// Function to call twitter api, called by route
var callAPI = function(callback) {
getAccessToken(callback());
console.log('callAPI function loaded');
};
var getAccessToken = function(callback) {
cb.__call(
"oauth2_token",
{},
function (reply, err) {
var accessToken;
console.log('1. response received');
if (err) {
console.log("error response or timeout exceeded" + err.error);
return;
}
if (reply) {
console.log('2. twitter: reply received');
console.log(reply);
accessToken = reply.access_token;
getUserTweets(accessToken, callback);
}
}
);
};
var getUserTweets = function(authToken, callback) {
console.log('passed accessToken');
console.log(authToken);
console.log(typeof callback);
cb.__call(
"users_show",
params,
function (reply, rate, err) {
if (err) {
console.log("Error in showing user or timed out" + err.error);
}
if (reply) {
console.log("received user's tweet");
var newTweetData = {
screen_name: reply.screen_name,
name: reply.name,
status_text: reply.status.text,
status_date: reply.status.created_at,
status_retweets: reply.status.retweet_count,
status_favourites: reply.status.favorite_count
};
console.log(newTweetData);
// final callback to set a variable
console.log(typeof callback);
setTweetData(newTweetData, callback);
// console.log('getUserTweets callback');
// callback;
}
}
);
};
var setTweetData = function(newTweetData, callback) {
if(!newTweetData) {
return 'variable is a string, not function';
}
tweetData = newTweetData;
console.log('tweet data has been set');
console.log(callback);
callback(tweetData);
};
module.exports = { params, callAPI };
Not sure what the proper way is to pass that callback from my route, through to the end of my function chain so that my res.send() triggers once I have acquired the twitter data.

You have to change callApi method to pass "callback" as parameter and not call it.
var callAPI = function(callback) {
getAccessToken(callback);
// and not getAccessToken(callback());
console.log('callAPI function loaded');
};
The difference is, in you code you are calling the callback just after authentication.
After my change, you pass the callback to getUserTweets function, and it will call the callback after tweets fetched.

Related

Elasticsearch.js - wait for ping to complete, async call

I am playing with elasticsearch.js from the browser. I would like to ping elasticsearch, wait for the request to complete and then return the result of the connection. But right now it is happening asynchronously, and returning undefined even when the connection is ok. I have code like this:
var connectionOK = false;
function createElasticsearchClient(hostAddress) {
var client = new $.es.Client({
hosts: hostAddress
});
return client;
}
function checkElasticsearchConnection(client) {
$.when(pingElasticsearch(client)).done(function () {
return connectionOK;
});
}
function pingElasticsearch(client) {
console.log("ELASTICSEARCH: Trying to ping es");
client.ping({
requestTimeout: 30000,
// undocumented params are appended to the query string
hello: "elasticsearch"
}, function (error) {
if (error) {
console.error('ELASTICSEARCH: Cluster is down!');
connectionOK = false;
console.log("INSIDE: " + connectionOK);
} else {
console.log('ELASTICSEARCH: OK');
connectionOK = true;
console.log("INSIDE: " + connectionOK);
}
});
}
and how it is used:
var esClient = createElasticsearchClient("exampleserver.com:9200");
var esCanConnect = (checkElasticsearchConnection(esClient));
You're mixing asynchronous functions with synchronous functions. You could go with this approach instead:
function createElasticsearchClient(hostAddress, callback) {
var client = new $.es.Client({
hosts: hostAddress
});
return callback(client);
}
function pingElasticsearch(client, callback) {
console.log("ELASTICSEARCH: Trying to ping es");
client.ping({
requestTimeout: 30000,
// undocumented params are appended to the query string
hello: "elasticsearch"
}, function (error) {
if (error) {
return callback('ELASTICSEARCH: Cluster is down!');
} else {
return callback(null);
}
});
}
And then run
createElasticsearchClient("exampleserver.com:9200", function(esClient) {
pingElasticsearch(esClient, function(err) {
if (err) console.log(err);
else {
//Everything is ok
console.log('All good');
}
});
});

Node.js how to use callback to send data to web interface

I have two functions that query the twitter api. The query is done through a web interface and I call each function when a checkbox is on. My problem now is after all the querying has been done, I want to be able to store the data and send it back to the web interface. How do I do this ?
if (string.checktweet1 == 'on') {
tweets(string.teamname)
}
if (string.checkmentions1 == 'on'){
mentions(string.teamname)
}
if (string.checktweet2 == 'on'){
tweets(string.playername)
}
if (string.checkmentions2 == 'on'){
mentions(string.playername)
}
function mentions(x){
client.get('search/tweets', {q:x, count:1},
function (err,data,){
for(var index in data.statuses){
var tweet = data.statuses[index];
console.log(tweet.text);
}
})
}
My code is only sending the data for the function "tweets"
json = {};
function tweets(y){
client.get('statuses/user_timeline', {screen_name:y, count:1},
function(err,data) {
for(var index in data){
var tweet = data[index];
console.log(tweet.text);
}
json[index] = tweet
res.end(JSON.stringify(json));
})
}
As I understand you are not looking to save the data, but just collect the results of multiple asynchronous calls and once all are completed deliver the data to your client? If so, you could use async or promises.
There are already examples of this in Stack Overflow, eg. this Node.js: Best way to perform multiple async operations, then do something else? but here anyways simplified implementations for both.
Using async
var async = require('async');
// ...
var tweets = function(y) {
return function(cb) {
client.get('statuses/user_timeline', {screen_name: y, count: 1},
function(err, data) {
// Process the data ...
cb(null, processedTweets);
}
);
}
};
var mentions = function(x) {
return function(cb) {
client.get('search/tweets', {q: x , count: 1},
function(err, data) {
// Process the data ...
cb(null, processedMentions);
}
);
}
};
app.get('/mytweetsapi', function(req, res) {
var tasks = [];
if (string.checktweet1 == 'on') {
tasks.push(tweets(string.teamname));
}
if (string.checkmentions1 == 'on'){
tasks.push(mentions(string.teamname));
}
if (string.checktweet2 == 'on'){
tasks.put(tweets(string.playername));
}
if (string.checkmentions2 == 'on'){
tasks.put(mentions(string.playername));
}
async.parallel(tasks, function(err, results) {
// Process the results array ...
res.json(processedResults);
});
});
Using promises
var Promise = require('bluebird');
// ...
var tweets = function(y) {
return new Promise(function(resolve, reject) {
client.get('statuses/user_timeline', {screen_name: y, count: 1},
function(err, data) {
// Process the data ...
resolve(processedTweets);
}
);
});
};
var mentions = function(x) {
return new Promise(function(resolve, reject) {
client.get('search/tweets', {q: x , count: 1},
function(err, data) {
// Process the data ...
resolve(processedMentions);
}
);
});
};
app.get('/mytweetsapi', function(req, res) {
var tasks = [];
// Same if this then tasks.push as in async example here
Promse.all(tasks).then(function(results) {
// Process the results array ...
res.json(processedResults);
});
});
I don't know what HTTP client you are using, but you can maybe use var client = Promise.promisifyAll(require('your-client-lib')); to convert the methods to return promises, and then you could convert the tweets and mentions functions to
var tweets = function(y) {
return client.get('statuses/user_timeline', {screen_name: y, count: 1});
};
This way though the results in Promise.all are mixed responses and you would need to identify which are tweets and which are mentions to process them properly.
For saving the data to a file without a database, you can use fs.writeFile():
fs.writeFile('message.txt', 'Hello Node.js', (err) => {
if (err) throw err;
console.log('It\'s saved!');
});
https://nodejs.org/api/fs.html#fs_fs_writefile_file_data_options_callback
Server/Node:
var app = require('http').createServer(handler)
var io = require('socket.io')(app);
var fs = require('fs');
app.listen(80);
function handler (req, res) {
fs.readFile(__dirname + '/index.html',
function (err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
});
}
io.on('connection', function (socket) {
if(twitterInfoReady){
socket.emit('twitterInfoIncoming', {
twitterInfo1: 'blah',
twitterInfo2: 'more data',
twitterInfo3: 'and more data'
});
}
});
Client
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io('http://localhost');
socket.on('twitterInfoIncoming', function (data) {
console.log(data.twitterInfo1);
});
</script>
Example taken from Socket.io docs, just modified a tiny bit http://socket.io/docs/

sails.js find queries with async.js each, parallel calls - each returning early

sails v0.11.0 (http://sailsjs.org/)
I have tried unsuccessfully to use .exec callback, promises (http://sailsjs.org/documentation/reference/waterline-orm/queries) and now async.js (https://github.com/caolan/async) to control the asynchronous flow revolving around looping over a find query. The async.each log output does not have the parallel work in it (although parallel does populate).
So if your solution works using .exec callback, promises or async.js - I will gladly take it!
I found this link gave some helpful examples of async.js (http://www.sebastianseilund.com/nodejs-async-in-practice)
Thank you for your time and assistance.
Below is my code using async:
/**
* #module :: Service
* #type {{findProfile: Function}}
*/
require('async');
module.exports = {
getProfile: function (userId, callback) {
var json = {};
json.notFound = false;
json.locations = {};
json.sports = {};
User.findOne({id: userId}).exec(function (err, user) {
if (err) {
json.notFound = true;
json.err = err;
}
if (!err) {
json.user = user;
UserSport.find({user_id: user.id}).exec(function (err, userSports) {
if (err) {
sails.log.info("userSports error: " + userSports);
}
async.each(userSports, function (userSport, callback) {
LocationSport.findOne({id:userSport.locationsport_id}).exec(function (err, locationSport) {
if (locationSport instanceof Error) {
sails.log.info(locationSport);
}
async.parallel(
[
function (callback) {
Location.findOne({id:locationSport.location_id}).exec(function (err, location) {
if (location instanceof Error) {
sails.log.info(location);
}
callback(null, location);
});
},
function (callback) {
Sport.findOne({id:locationSport.sport_id}).exec(function (err, sport) {
if (sport instanceof Error) {
sails.log.info(sport);
}
callback(null, sport);
});
}
],
function (err, results) {
if (!(results[0].id in json.locations)) {
json.locations[results[0].id] = results[0];
}
if (!(results[1].id in json.sports)) {
json.sports[results[1].id] = results[1];
}
}
); // async.parallel
}); // locationSport
callback();
}, function (err) {
sails.log.info('each');
sails.log.info(json);
}); // async.each
}); // UserSport
}
}); // User
}
}
The structure of your code is the following :
async.each(userSports, function (userSport, callback) {
// Whatever happen here, it runs asyncly
callback();
}, function (err) {
sails.log.info('each');
sails.log.info(json);
}); // async.each
You are calling the callback method, but the processing on your data is not yet done (it is running async-ly). As a result, sails.log.info is called immediatly.
You should modify your code so the callback is called once the process is done. i.e in the result of your async.parallel :
async.each(userSports, function (userSport, outer_callback) {
LocationSport.findOne({id:userSport.locationsport_id}).exec(function (err, locationSport) {
//...
async.parallel(
[
function (callback) {
// ...
},
function (callback) {
// ...
}
],
function (err, results) {
// ...
outer_callback();
}
); // async.parallel
}); // locationSport
}, function (err) {
sails.log.info('each');
sails.log.info(json);
}); // async.each

Nodejs exports.module How to export Variable to other Script

my goal is to get a list of files from a google drive folder and its subfolders as json string. so i can then use express to expose it as an API endpoint that other applications can connect to it.
the code is working. i get everything i want, but i do not know how to export my data variable to app.js
// get-filelist.js
var GoogleTokenProvider = require("refresh-token").GoogleTokenProvider,
request = require('request'),
async = require('async'),
data
const CLIENT_ID = "514...p24.apps.googleusercontent.com";
const CLIENT_SECRET = "VQs...VgF";
const REFRESH_TOKEN = "1/Fr...MdQ"; // get it from: https://developers.google.com/oauthplayground/
const FOLDER_ID = '0Bw...RXM';
async.waterfall([
//-----------------------------
// Obtain a new access token
//-----------------------------
function(callback) {
var tokenProvider = new GoogleTokenProvider({
'refresh_token': REFRESH_TOKEN,
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET
});
tokenProvider.getToken(callback);
},
//-----------------------------
// connect to google drive, look for the folder (FOLDER_ID) and list its content inclusive files inside subfolders.
// return a list of those files with its Title, Description, and view Url.
//-----------------------------
function(accessToken, callback) {
// access token is here
console.log(accessToken);
// function for token to connect to google api
var googleapis = require('./lib/googleapis.js');
var auth = new googleapis.OAuth2Client();
auth.setCredentials({
access_token: accessToken
});
googleapis.discover('drive', 'v2').execute(function(err, client) {
getFiles()
function getFiles(callback) {
retrieveAllFilesInFolder(FOLDER_ID, 'root' ,getFilesInfo);
}
function retrieveAllFilesInFolder(folderId, folderName, callback) {
var retrievePageOfChildren = function (request, result) {
request.execute(function (err, resp) {
result = result.concat(resp.items);
var nextPageToken = resp.nextPageToken;
if (nextPageToken) {
request = client.drive.children.list({
'folderId': folderId,
'pageToken': nextPageToken
}).withAuthClient(auth);
retrievePageOfChildren(request, result);
} else {
callback(result, folderName);
}
});
}
var initialRequest = client.drive.children.list({
'folderId': folderId
}).withAuthClient(auth);
retrievePageOfChildren(initialRequest, []);
}
function getFilesInfo(result, folderName) {
result.forEach(function (object) {
request = client.drive.files.get({
'fileId': object.id
}).withAuthClient(auth);
request.execute(function (err, resp) {
// if it's a folder lets get it's contents
if(resp.mimeType === "application/vnd.google-apps.folder"){
retrieveAllFilesInFolder(resp.id, resp.title, getFilesInfo);
}else{
/*if(!resp.hasOwnProperty(folderName)){
console.log(resp.mimeType);
}*/
url = "http://drive.google.com/uc?export=view&id="+ resp.id;
html = '<img src="' + url+ '"/>';
// here do stuff to get it to json
data = JSON.stringify({ title : resp.title, description : resp.description, url : url});
//console.log(data);
//console.log(resp.title);console.log(resp.description);console.log(url);
//.....
}
});
});
}
});
}
]);
// export the file list as json string to expose as an API endpoint
console.log('my data: ' + data);
exports.files = function() { return data; };
and in my app.js i use this
// app.js
var jsonData = require('./get-filelist');
console.log('output: ' + jsonData.files());
the data variable in app.js doesnt contain any data, while checking the output inside the function getFilesInfo() is working.
so, how to make my data variable accessible in other scripts?
You've got a problem with sync/async behavior.
app.js should be aware when to call the files() function exported from get-filelist. The code you've got there calls the files() function immediately after requiring the get-filelist module. At that moment the data variable is still empty.
Best solution would be to provide the files() function with a callback that will trigger once you've loaded the data variable.
Of course, you will need some extras:
the loaded flag so that you know whether to trigger the callback immediately (if data is already loaded) or postpone the trigger once the load is done.
the array for waiting callbacks that will be triggered upon load (callbacks).
// get-filelist.js
var GoogleTokenProvider = require("refresh-token").GoogleTokenProvider,
request = require('request'),
async = require('async'),
loaded = false, //loaded? Initially false
callbacks = [], //callbacks waiting for load to finish
data = [];
const CLIENT_ID = "514...p24.apps.googleusercontent.com";
const CLIENT_SECRET = "VQs...VgF";
const REFRESH_TOKEN = "1/Fr...MdQ"; // get it from: https://developers.google.com/oauthplayground/
const FOLDER_ID = '0Bw...RXM';
async.waterfall([
//-----------------------------
// Obtain a new access token
//-----------------------------
function(callback) {
var tokenProvider = new GoogleTokenProvider({
'refresh_token': REFRESH_TOKEN,
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET
});
tokenProvider.getToken(callback);
},
//-----------------------------
// connect to google drive, look for the folder (FOLDER_ID) and list its content inclusive files inside subfolders.
// return a list of those files with its Title, Description, and view Url.
//-----------------------------
function(accessToken, callback) {
// access token is here
console.log(accessToken);
// function for token to connect to google api
var googleapis = require('./lib/googleapis.js');
var auth = new googleapis.OAuth2Client();
auth.setCredentials({
access_token: accessToken
});
googleapis.discover('drive', 'v2').execute(function(err, client) {
getFiles()
function getFiles(callback) {
retrieveAllFilesInFolder(FOLDER_ID, 'root' ,getFilesInfo);
}
function retrieveAllFilesInFolder(folderId, folderName, callback) {
var retrievePageOfChildren = function (request, result) {
request.execute(function (err, resp) {
result = result.concat(resp.items);
var nextPageToken = resp.nextPageToken;
if (nextPageToken) {
request = client.drive.children.list({
'folderId': folderId,
'pageToken': nextPageToken
}).withAuthClient(auth);
retrievePageOfChildren(request, result);
} else {
callback(result, folderName);
}
});
}
var initialRequest = client.drive.children.list({
'folderId': folderId
}).withAuthClient(auth);
retrievePageOfChildren(initialRequest, []);
}
function getFilesInfo(result, folderName) {
data = []; //data is actually an array
result.forEach(function (object) {
request = client.drive.files.get({
'fileId': object.id
}).withAuthClient(auth);
request.execute(function (err, resp) {
// if it's a folder lets get it's contents
if(resp.mimeType === "application/vnd.google-apps.folder"){
retrieveAllFilesInFolder(resp.id, resp.title, getFilesInfo);
}else{
/*if(!resp.hasOwnProperty(folderName)){
console.log(resp.mimeType);
}*/
url = "http://drive.google.com/uc?export=view&id="+ resp.id;
html = '<img src="' + url+ '"/>';
// here do stuff to get it to json
data.push(JSON.stringify({ title : resp.title, description : resp.description, url : url}));
//console.log(resp.title);console.log(resp.description);console.log(url);
//.....
}
});
});
//console.log(data); //now, that the array is full
//loaded is true
loaded = true;
//trigger all the waiting callbacks
while(callbacks.length){
callbacks.shift()(data);
}
}
});
}
]);
// export the file list as json string to expose as an API endpoint
console.log('my data: ' + data);
exports.files = function(callback) {
if(loaded){
callback(data);
return;
}
callbacks.push(callback);
};
Now the app.js behavior needs to change:
// app.js
var jsonData = require('./get-filelist');
jsonData.files(function(data){
console.log('output: ' + data);
});
/* a much more elegant way:
jsonData.files(console.log.bind(console,'output:'));
//which is actually equivalent to
jsonData.files(function(data){
console.log('output: ',data); //without the string concatenation
});
*/

Node js custom callback function error

I'm trying to make a simple authentication with node js. Because I read user data from a database, I have to make it asynchronous. Here's my function, which checks if authentication is ok:
function auth(req, callback) {
var header = req.headers['authorization'];
console.log(cb.type);
console.log("Authorization Header is: ", header);
if(!header) {
callback(false);
}
else if(header) {
var tmp = header.split(' ');
var buf = new Buffer(tmp[1], 'base64');
var plain_auth = buf.toString();
console.log("Decoded Authorization ", plain_auth);
var creds = plain_auth.split(':');
var name = creds[0];
var password = creds[1];
User.findOne({name:name, password:password}, function(err, user) {
if (user){
callback(true);
}else {
callback(false);
}
});
}
}
And here I call the function:
auth (req, function (success){
if (!success){
res.setHeader('WWW-Authenticate', 'Basic realm="myRealm');
res.status(401).send("Unauthorized");
}else{
if(user!==req.user) {
res.status(403).send("Unauthorized");
}else{
User.findOneAndUpdate({user:userid}, {user:req.body.user, name:req.body.name, email:req.user.email, password:User.generateHash(req.body.password)},
{upsert:true}, function(err, user) {
if(!err) {
res.status(200).send("OK");
}else{
res.status(400).send("Error");
}
});
}
}
});
This gives me error "TypeError: object is not a function", pointing at "callback(false)". I have no idea what could cause this error, as I pass a function as a parameter, and the first log message prints "[function]". Any help would be appreciated.

Categories

Resources