Understanding callbacks in Javascript and node.js - javascript

I'm a long time PHP (CodeIgniter & WordPress) developer that only recently wanted to learn a few other languages. I've set out to learn Ruby (on Rails, and Sinatra), Python (w/ Flask framework) and Javascript with node.js.
I decided to create the most basic application I can think of, a URL expander, using each of these languages. I have managed to create a working version in every language, except node.js and Javascript.
I kinda know my problem, I know it is related to callbacks. I know I'm not doing it right. I get the basic idea of callbacks, but I just cannot figure out how to fix this mess I have created.
This is my whole code:
var http = require('http');
var url = require('url');
function expand() {
var short = url.parse('http://t.co/wbDrgquZ');
var options = {
host: short.hostname,
port: 80,
path: short.pathname
};
function longURL(response) {
console.log(response.headers.location);
}
http.get(options, longURL);
}
function start() {
function onRequest(request, response) {
console.log("Request received.");
response.writeHead(200, {
"Content-Type": "text/plain"
});
response.write("Hello World");
expand();
response.end();
}
http.createServer(onRequest).listen(8888);
console.log("Server has started.");
}
start();
The server starts, and when a request is made, it calls the expand function which returns the expanded URL in the terminal. I'm trying to get it to print in the browser.
Any help is appreciated. Thanks in advance.

You've made a few flaws.
You should rewrite expand to pass the url in and pass a callback in. Any function that does anything asynchronous generally has the signature (data, callback) in node. This basically allows you to say I want this function to do something then tell me when it's done.
function expand(urlToParse, callback) {
// note we pass in the url this time
var short = url.parse(urlToParse);
var options = {
host: short.hostname,
port: 80,
path: short.pathname
};
// note we store the clientRequest object temporarily
var clientRequest = http.get(options, extractRealURL);
// Always attach the error handler and forward any errors
clientRequest.on("error", forwardError);
function extractRealURL(res) {
callback(null, res.headers.location);
}
function forwardError(error) {
callback(err);
}
}
Here the callback is expected to have the signature of (err, data) which almost all callbacks in node have. We've also added error handling which is a must.
We now change onRequest to actually call expand properly
function onRequest(request, response) {
// parse the incoming url. true flag unpacks the query string
var parsedUrl = url.parse(request.url, true),
// extract the querystring url.
// http://localhost:8888/?url=http://t.co/wbDrgquZ
urlToExpand = parsedUrl.query.url;
// call expand with the url and a callback
expand(urlToExpand, writeResponse);
function writeResponse(error, newUrl) {
// handle the error case properly
if (error) {
response.writeHead(500, { 'Content-Type': 'text/plain'});
// early return to avoid an else block
return response.end(error.message);
}
response.writeHead(200, { 'Content-Type': 'text/plain'});
// write the new url to the response
response.end(newUrl);
}
}
Here we have added error handling logic and also unpacked the actual url to expand from the query string.
Generally the pattern of doSomething<data, callback<err, result>> works very well in node.js.
It's the exact same as let result = doSomething<data> mayThrow err that you expect in your normal blocking languages except it's asynchronous.
Note that the alternative option of passing the ServerResponse object into the function is frowned upon, by doing so your creating unnecessary hard coupling between the expand function and the server response.
The expand function should only expand an url and return the expanded url, it has no business doing IO itself.
Full code

A callback is just a word to describe a function that we pass to some other code for that other code to invoke.
In your example, onRequest is a callback function that gets passed to createServer to be invoked whenever a request is received.
I think the issue you're having is that you're expecting expand() to have access to all the same variables/parameters that the onRequest function has access to. This isn't the case.
You need pass the response object to expand(). Because the call to expand creates a new callback longURL for the http.get call, it will have access to the response object that you passed in.
function expand( resp ) {
// receive the original response object, and end the response when ready
var short = url.parse('http://t.co/wbDrgquZ');
var options = {
host: short.hostname,
port: 80,
path: short.pathname
};
function longURL( response ) {
console.log(response.headers.location);
resp.end( response.headers.location ); // end the original response
}
http.get(options, longURL);
}
function start() {
function onRequest(request, response) {
console.log("Request received.");
response.writeHead(200, {
"Content-Type": "text/plain"
});
response.write("Hello World");
expand( response ); // pass this response object to expand
}
http.createServer(onRequest).listen(8888);
console.log("Server has started.");
}

You weren't sending the response as a parameter to the expand function and also you were calling response.end() before the expand() function could write anything, here's the corrected version:
var http = require('http');
var url = require('url');
function expand(res) {
var short = url.parse('http://t.co/wbDrgquZ');
var options = {
host: short.hostname,
port: 80,
path: short.pathname
};
function longURL(response){
console.log(response.headers.location);
res.end("<br />" + response.headers.location);
}
http.get(options, longURL);
}
function start() {
function onRequest(request, response) {
console.log("Request received.");
response.writeHead(200, {"Content-Type": "text/html"});
response.write("Hello World");
expand(response);
}
http.createServer(onRequest).listen(8888);
console.log("Server has started.");
}
start();

Related

Javascript: ajax trouble with requests arrays

I'm fairly new to javascript and I'm still trying to grasp callback concepts. Even then, I'm not sure I have an issue with callbacks or ajax itself.
So I have this callback function which basically takes an array of objects somewhere else in my code.
function powerAmplifierInfo(id, cb) {
ApiGet('/system/radio-frequency/tx/' + id + '/power-amplifier', function (err, result) {
if (err && !TESTING) {
alertError("Connection error: " + err.statusText);
return;
} else if (result && result.result.code !== "OK") {
alertError(result.result.message);
return;
}
if (TESTING) {
result = { powerAmplifier: "TEST model", uptime: Date.now() };
}
cb(result.powerAmplifier);
});
}
This function uses another function which uses ajax to send a get request to some server.
function ApiGet(urlSuffix, cb) {
var url = apiPrefix + "/" + urlSuffix;
$.ajax({
url: url,
type: 'GET',
success: function (result) {
cb(null, result);
},
error: function (err) {
cb(err, null);
}
});
}
Now, the request chains in my list works but sometimes requests get mixed up together when I rerun the code in loop as it seems ajax switch some port orders while sending requests. I found out this issue while troubleshooting my local network with Wireshark. Here's an image to show it:
The Get requests are sent but between the 3rd and the 4th ones, there is a port mixup. As we can see, the server replies without the port order mixup.
I'm wondering, is this a known issue or would there be a problem with my code, or even my server? I do not get this error when sending a single object in my callback functions instead of an object array.
Thanks in advance!

Is there a way to get the response for a specific emit using socket.io?

I need to perform GET requests on a server that doesn't have a CORS header, so I created a very simple node app that will do the GET for me:
var io = require('socket.io')();
var request = require('request');
io.on('connection', function(socket) {
socket.on('get', function(url) {
request(url, function(error, response, body) {
socket.emit('response', body);
});
});
});
On the client side, I'm using it with a simple get function:
var socket = io();
function get(url) {
return new Promise(function(resolve) {
socket.emit('get', url);
socket.once('response', function(data) {
resolve(data);
});
}
}
This works great when I request a URL one at a time:
get('http://some/resource/1');
But when I try multiple requests to different endpoints at the same time, they will all return the same data:
get('http://some/resource/1');
get('http://some/resource/2');
get('http://some/resource/3');
In this case, the data for /resource/1 is returned for all 3. I understand that this is because the socket.once('response') callback is getting added immediately for all 3 requests, and when the data is returned for the first one, all 3 callbacks are invoked for it.
Is there a way that I can get it so that each socket.emit() and socket.on() are paired to each other? I know that I can add a unique ID to each emit and pass it back on the response, or use the unique ID in the message itself (something like socket.emit('get:a54jk14hf'), but I'm hoping for a simpler solution if there is one.
The issue is that you are overwriting the socket.io's "response" event by issuing the "once" hook inside the "get" function. It's fine to keep it in there, but you'll need to provide a unique event for "once", that way four unique events will come back. This could be done using the "url" as key. It would look something like this:
Server:
var io = require('socket.io')();
var request = require('request');
io.on('connection', function(socket) {
socket.on('get', function(url) {
request(url, function(error, response, body) {
socket.emit(
'response-' + url, // USING THIS INSTEAD OF JUST "response"
body
);
});
});
});
Client:
var socket = io();
function get(url) {
return new Promise(function(resolve) {
socket.emit('get', url);
socket.once(
'response-' + url, // USING THIS INSTEAD OF JUST "response"
function(data) {
resolve(data);
});
}
}
I'm not sure if this was a requirement of yours, but the above approach would also enable you to have the correct data always come back to the Promise's "then" block.
get('http://some/resource/1').then(function (data) {
// the "data" is now guaranteed to be from resource "1"
// previous, it could have been from 1, 2, or 3
})

Angular with hapi js server jsonp

I have an endpoint defined at /api/profile which accepts post parameters.
var http = require('http');
var serverConfig = require('../server.config.js');
var request = require('request');
module.exports = function(server){
server.route({
method: 'POST',
path: '/api/profile',
handler: getProfileData
});
function getProfileData(request, reply){
var battleTag = request.payload.battleTag;
getProfileDataHttp(battleTag, function(err, data){
if(err){
reply(new Error(err));
}
reply(data);
});
}
function getProfileDataHttp(battleTag, callback){
var key = serverConfig.battleNet.apiKey;
var tag = encodeURIComponent(battleTag);
var url = 'https://eu.api.battle.net/d3/profile/'+ tag + '/?locale=en_GB&callback=JSON_CALLBACK&apikey=' + key;
console.log(url);
request(url,function(error, response, body){
if(error){
callback(err);
}
if(!error && response.statusCode ==200){
callback(null, body);
}
});
}
};
it is calling an api with a json callback, when I am receiving the data it is in format:
JSON_CALLBACK({ json data here})
how can I get this endpoint to return just the json data, I have tried JSON.parse() but it causes errors in the server.
the angular service that calls this endpoint is like below:
function getProfileData(battleTag){
var defer = $q.defer();
var tag = validTag(battleTag);
if(!tag){
defer.reject('Invalid Tag please use format 1[a-z]11[a-z0-9]#4[0-9]');
return defer.promise;
}
$http.post('/api/profile', {
battleTag: battleTag
})
.success(function(data){
if(data.reason){
defer.resolve(data.reason);
}
defer.resolve(data);
})
.error(function(err){
defer.reject(err);
});
return defer.promise;
}
the call would work when using $http.jsonp in angular however I had to create the server to hide the secret key from the client
Your question is a bit confusing. You are talking about JSONP, but you want to fetch the data directly.
The whole point of JSONP is to return the data encapsulated inside a function that you choose. You then simply have to execute it.
If you want the data in a direct way, don't use JSONP. Simply do a "normal" call.
After having a quick look at the Battle.net API, it seems that to get the data directly, you should simply omit the 'callback' parameter in the URL of your request.
Thus, your request URL would looks like that:
var url = 'https://eu.api.battle.net/d3/profile/'+ tag + '/?locale=en_GB&apikey=' + key;

How do I know which request belongs to what partial data in node.js

I have the node.js server below. Suppose that multiple clients make a POST request. But these will share the same variable body and the final string will become meaningless. Is is possible to identify a request by id or something? All I have is request.headers, but that does not contain any unique information.
var http = require("http");
var server = http.createServer();
server.on("request", onRequest);
server.listen(8001, "127.0.0.1");
function onRequest(request, response) // assuming "POST"
{
var body = "";
request.on("data", function (data) { body = body + data; });
request.on("end", function () { console.log(body); });
}
Functions in general inherit scopes (from their parents), so each body variable will be correctly concatenated for each request.

Abstracting the making of requests with Node.js

Traditionally I use jQuery for all my JS code, but I'm tasked to launch a simple API with node.js. Today is my first day with Node but I know enough about JS and closures to do OK. One of the tasks of the API is to authenticate across a third party service and being a python guy, I wanted to abstract all my outbound request calls like so:
EDIT
var http = require('http');
var init = function(nconf) {
var methods = {
/*
Helper method to create the request header
*/
headers: function(params) {
var content = JSON.stringify(params);
return {
'Content-Type': 'application/json',
'Content-Length': content.length
}
},
/*
Helper method to create the options object
which is used in making any type of
outbound http request
*/
options: function(host, path, method, params) {
return {
host: host,
port: 80,
path: path,
method: method,
headers: methods.headers(params)
}
},
/*
Helper method to abstract the making of
outbound http requests
*/
call: function(options, params, success, err) {
var req = http.request(options, success);
req.on('error', err);
req.write(params);
req.end();
},
/*
Helper method to parse the response
and return a json object
*/
parse: function(res, result) {
var responseString = '';
res.on('data', function(data) {
responseString += data;
});
res.on('end', function() {
result = JSON.parse(responseString);
});
},
/*
API method to return the latest
release and tag names
*/
latest: function(req, res, next){
// // var url = nconf.get('prod:authenticate');
//authenticate the test user
msg = methods.authenticate(nconf.get('test:user'), nconf.get("test:password"));
res.send(msg);
next();
},
/*
Method used by this API to authenticate users.
It is used to de-couple this API from the Database
Schema by calling out to the TTCPAS App and requesting it
to handle the authentication
*/
authenticate: function(username, password){
// create post parameters with API key
var params = {"username": username, "password": password, "api_key": nconf.get('api_key')};
//construct options object with params and header
var options = methods.options(nconf.get('ttcpas:host'), nconf.get('ttcpas:auth_url'), 'POST', params);
var result;
var success = function(res) {
res.setEncoding('utf-8');
methods.parse(res, result);
};
methods.call(options, params, success, function(err){});
while (typeof(result.statusCode) == 'undefined') {
//wait 1 second;
setTimeout(function(){
console.log("waiting on request at " + nconf.get('ttcpas:host') + nconf.get('ttcpas:auth_url'));
}, 1000);
}
//then down here
if (result.statusCode == 200) {return result};//success
if (result.statusCode == 403) {return "forbidden"};//forbidden
}
}
return methods;
};
module.exports.init = init;
#jfriend00 As I said I don't know how node.js is supposed to be styled. I wanted to just abstract as much as possible to make the code clean and reusable
Now when I do http://localhost:9000/latest/
I get:
{"code":"InternalError","message":"first argument must be a string or Buffer"}
Uhhh, this part will simply not work:
while (typeof(result.statusCode) == 'undefined') {
//wait 1 second;
setTimeout(function(){
console.log("waiting on request at " + nconf.get('ttcpas:host') + nconf.get('ttcpas:auth_url'));
}, 1000);
}
If result.statusCode is ever undefined, this will spin forever piling up setTimeout() calls in the event queue until eventually something fills up or you run out of memory.
Because node.js is primarily single threaded, you can't loop waiting for something to change. Because you never finish this while loop, no other node.js code gets to run so result.statusCode can never change. Thus, you have an infinite loop here.
All of your nodejs code needs to be event driven, not spin/wait loops. FYI, this is similar to browser-based Javascript.

Categories