Trying to understand function and callback scope by filling out objects - javascript

I have make some functions to retrieve data using the Github API. I have the callbacks in place to get the data but I am sure how to understand where a function exits and when I stops modifying things.
For example in the code below, in the first function, when the AJAX call is successful, the callback is executed in the second function where the data is manipulated. Does that mean the the return in the first function is not needed or used? And in the second function is the data used and pushed to the array and then the array returned or is it the other way around where the (empty) array is returned and then the callback does its thing.
I am ultimately trying to get the data from the callback into an object and return that filled object from the parent function.
function makeAJAXCall(hash, cb) {
var returnedJSON, cb = cb, hash = hash;
$.ajax({
accepts: 'application/vnd.github-blob.raw',
dataType: 'jsonp',
url: hash,
success: function (json) {
console.info(json);
returnedJSON = json;
// Time for callback to be executed
if (cb) {
cb(json);
}
},
error: function (error) {
console.error(error);
// an error happened, check it out.
throw error;
}
});
return returnedJSON;
}
function parseBlob(hash) {
var objectedJSON, objectList = [], i;
objectedJSON = makeAJAXCall(hash, function (objectedJSON) { // no loop as only one entry
objectList.push(objectedJSON.content);
});
return objectList;
}
function walkTree(hash) {
var objectedJSON, objectList = [], i, entry;
var hash = 'https://api.github.com/repos/myAccountName/repo/git/trees/' + hash;
objectedJSON = makeAJAXCall(hash, function (objectedJSON) {
for (i = 0; i < objectedJSON.data.tree.length; i += 1) {
entry = objectedJSON.data.tree[i];
console.debug(entry);
if (entry.type === 'blob') {
if (entry.path.slice(-4) === '.svg') { // we only want the svg images not the ignore file and README etc
console.info(entry.path)
objectList.push(parseBlob(entry.url));
}
} else if (entry.type === 'tree') {
objectList.push(walkTree(entry.sha));
}
}
});
console.info(objectList);
return objectList;
}
$(document).ready(function () {
var objects = walkTree('master', function () { // master to start at the top and work our way down
console.info(objects);
});
});

Here you are making an AJAX call A refers to asynchronous, ie your success/error callback will be executed asynchronously.
makeAJAXCall will return before executing success/error of $ajax.
so the objectedJSON = makeAJAXCall will return you undefined
function makeAJAXCall(hash, cb) {
$.ajax({
accepts: 'application/vnd.github-blob.raw',
dataType: 'jsonp',
url: hash,
success: function (json) {
// this function will be executed after getting response from server
//ie Asynchronously
//here cb passed from the makeAjaxCall exist in the closure scope
if (cb) {
cb(json);
}
},
error: function (error) {
console.error(error);
// an error happened, check it out.
throw error;
}
});
}
Now when you call makeAjaxCall the callback function you are passing will exist in the closure scope of $.ajax and will be executed on success of server response
makeAJAXCall(hash, function (objectedJSON) {
//objectJSON contains the response from server
// do all your operations using server response over here or assign it to a global variable
});
check below links
https://developer.mozilla.org/en/JavaScript/Guide/Closures
https://mikewest.org/2009/05/asynchronous-execution-javascript-and-you
or you can make your ajax call in sync using async:false which is highly not recommended
function makeAJAXCall(hash, cb) {
var returnedJSON;
$.ajax({
accepts: 'application/vnd.github-blob.raw',
dataType: 'json',
async : false, //this will make it in sync
url: hash,
success: function (json) {
console.info(json);
returnedJSON = json;
//now makeAJAXCall will wait for success to complete and it will return only after executing success/error
// Time for callback to be executed
if (cb) {
cb(json);
}
},
error: function (error) {
console.error(error);
// an error happened, check it out.
throw error;
}
});
//will wait for success/error before returning
return returnedJSON;
}
In the above case your code will work

function makeAJAXCall(hash, cb) {
var returnedJSON, cb = cb, hash = hash;
return $.ajax({
accepts: 'application/vnd.github-blob.raw',
dataType: 'jsonp',
url: hash,
success: function (json) {
console.info(json);
returnedJSON = json;
// Time for callback to be executed
if (cb) {
cb(json);
}
},
error: function (error) {
console.error(error);
// an error happened, check it out.
throw error;
}
});
}
function parseBlob(hash) {
var objectedJSON, objectList = [], i;
objectedJSON = makeAJAXCall(hash, function (objectedJSON) { // no loop as only one entry
objectList.push(objectedJSON.content);
});
return objectList;
}
function walkTree(hash) {
var objectedJSON, objectList = [], i, entry;
var hash = 'https://api.github.com/repos/myAccountName/repo/git/trees/' + hash;
objectedJSON = $.when(maxAJAXCall)
.then(function(){
//Write the callback
});
Use $.when().then() to call ajax and manage the callbacks better.
.When

Related

Callbacks within http request methods - not happening in correct order

I've written a program that makes an HTTP GET request for three distinct URLs. The program is supposed to output the message body in the order the URLs are provided, however it's not doing so even though I'm making callbacks in exactly that order.
The final program is supposed to require the user to input the URLs via command line, however I've simply made variable assignments for ease of testing.
I realize this code could be more object-oriented - however I'm new to JavaScript and it's not my focus to learn how at the moment
var http = require('http')
// var url_1 = process.argv[2]
// var url_2 = process.argv[3]
// var url_3 = process.argv[4]
var url_1 = 'http://youvegotmail.warnerbros.com/cmp/0frameset.html'
var url_2 = 'http://www.3riversstadium.com/index2.html'
var url_3 = 'http://toastytech.com/evil/'
var output_1 = ''
var output_2 = ''
var output_3 = ''
function getHttp_1 (callback) {
http.get(url_1, function getResponse (response1) {
response1.setEncoding('utf8')
response1.on('data', function (data) {
output_1 = output_1 + data
})
response1.on('end', function processData() {
console.log("Printing Result 1:")
callback(output_1)
})
})
}
function getHttp_2 (callback) {
http.get(url_2, function getResponse (response2) {
response2.setEncoding('utf8')
response2.on('data', function (data) {
output_2 = output_2 + data
})
response2.on('end', function processData() {
console.log("Printing Result 2:")
callback(output_2)
})
})
}
function getHttp_3 (callback) {
http.get(url_3, function getResponse (response3) {
response3.setEncoding('utf8')
response3.on('data', function (data) {
output_3 = output_3 + data
})
response3.on('end', function processData() {
console.log("Printing Result 3:")
callback(output_3)
})
})
}
function printResults(output) {
console.log("Result")
// console.log(output)
}
getHttp_1(printResults)
getHttp_2(printResults)
getHttp_3(printResults)
EDIT:
Results I'm generally getting:
Printing Result 3:
Result
Printing Result 2:
Result
Printing Result 1:
Result
Results I'm expecting:
Printing Result 1:
Result
Printing Result 2:
Result
Printing Result 3:
Result
In contrast to the sequential callback approach proposed by some answers, using Promises will make this both more efficient (the requests will be made in parallel) and simpler:
var http = require('http'),
urls = [
'http://youvegotmail.warnerbros.com/cmp/0frameset.html',
'http://www.3riversstadium.com/index2.html',
'http://toastytech.com/evil/'
];
Promise.all(urls.map(getUrl))
.then(function (results) {
results.forEach(function (output, i) {
console.log("Result #" + (i + 1) +
" with length: " + output.length);
});
});
function getUrl(url, i) {
return new Promise(function (resolve, reject) {
http.get(url, function getResponse(resp) {
var output = '';
resp.setEncoding('utf8');
resp.on('data', function (data) {
output += data;
});
resp.on('end', function processData() {
console.log("Resolving Result " + (i + 1) + ":");
resolve(output);
});
})
});
}
Welcome to the asynchronous life of node.js! As you fire off those HTTP requests, one will not wait for the request before it to finish before it fires. You are seeing this odd behavior because you are practically sending all 3 requests at once, and simply printing as you see the responses.
Edit: If you do want to see them in correct order, fire off the second HTTP request inside the callback of the first, and then the third inside the callback of the second. That guarantees you won't get the data until after each one before it finishes.
function getHttp_1 (callback) {
http.get(url_1, function getResponse (response1) {
response1.setEncoding('utf8')
response1.on('data', function (data) {
output_1 = output_1 + data
})
response1.on('end', function processData() {
console.log("Printing Result 1:")
callback(output_1)
getHttp_2(callback)
})
})
}
The async module can really help for controlling how async tasks are executed. For example, if you want your requests to happen one after the other:
async.series([
function (next) { makeRequest(url_1, next); },
function (next) { makeRequest(url_2, next); },
function (next) { makeRequest(url_3, next); },
], function (err, result) {
// All done
});
// Or you can get fancy
//async.series([
// makeRequest.bind(null, url_1),
// makeRequest.bind(null, url_2),
// makeRequest.bind(null, url_3),
//]);
function makeRequest(url, callback) {
http.get(url, function getResponse (res) {
var output = '';
res.setEncoding('utf8')
res.on('data', function (data) {
output += data
})
response1.on('end', function processData() {
callback(output)
})
})
}
If you don't care what order they occur in but want to output them in order:
async.parallel([
function (next) { makeRequest(url_1, next); },
function (next) { makeRequest(url_2, next); },
function (next) { makeRequest(url_3, next); },
], function (err, results) {
if (err) {
return void console.error('Got an error:', err.stack);
}
console.log(results); // Will output array of every result in order
});
If the requests are dependent on each other, async.auto is useful to tie the result of one request to the request of another.
JavaScript/AJAX calls are async so don't follow the order you call them. To call them in sequence/specific order, do like:
$(function () {
//setup an array of AJAX options, each object is an index that will specify information for a single AJAX request
var ajaxes = [{ url : '<url>', dataType : 'json' }, { url : '<url2>', dataType : 'utf8' }],
current = 0;
//declare your function to run AJAX requests
function do_ajax() {
//check to make sure there are more requests to make
if (current < ajaxes.length) {
//make the AJAX request with the given data from the `ajaxes` array of objects
$.ajax({
url : ajaxes[current].url,
dataType : ajaxes[current].dataType,
success : function (serverResponse) {
...
//increment the `current` counter and recursively call this function again
current++;
do_ajax();
}
});
}
}
//run the AJAX function for the first time once `document.ready` fires
do_ajax();
});
Another option could be:
function callA() {
$.ajax({
...
success: function() {
//do stuff
callB();
}
});
}
function callB() {
$.ajax({
...
success: function() {
//do stuff
callC();
}
});
}
function callC() {
$.ajax({
...
});
}
callA();
Ref: Multiple Calls in Order

How to know when async function call is finished (Javascript)

If I have a function call callFunc(), and inside that function there is an async call (like a $http in Angular)... from where I call callFunc() is there some way for me to know when the function - including all of the async within it - has finished executing?
Async function won't technically returns an indicator for "Completed" before it completes. If you have the control for the service controller, you can send to client side a response size first and for each data chunk you send to the client side, you can make your own progress bar to see when it will complete. But other than calling the a callback function, there is no easy way to see it is finished or not.
Seriously, what is your use case?
You can use promise in angular.
function callFunc() {
var deferred = $q.defer();
$http({
method: 'GET',
url: '/someUrl'
}).then(function successCallback(response) {
deferred.resolve('success!');
}, function errorCallback(response) {
deferred.reject('failed.');
});
return deferred.promise;
}
var promise = callFunc();
promise.then(function(res) {
alert('successCallback: ' + res);
}, function(res) {
alert('errorCallback: ' + res);
}, function(res) {
alert('notifyCallback: ' + res);
});
Depending on what you want to do, and the asynch function invovled
calling an asynch function that takes an optional callback
function callFunc(callback) {
// this ones easy, provide a callback or not, doesn't matter
asynchFunction(callback);
}
calling an asynch function that requires a callback function
function callFunc(callback) {
// provide a dummy callback if one isn't provided
callback = callback || function() {};
asynchFunction(callback);
}
calling an asynch function and manioulating the results before calling the optionally provided callback
function callFunc(callback) {
function localCallback(param1, param2) {
var manipulateParams = param1 + param2;
if(typeof callback == 'function') {
callback(manipulateParams );
}
}
asynchFunction(localCallback);
}
alternative: using promises
in the case of $http
function callFunc() {
return $http({
method: 'GET',
url: 'url'
});
}
In the case of a function that doesn't return a promise, lets say it uses the "node" convention
function callFunc() {
var deferred = $q.defer();
somefunc(param, function(err, result) {
if(err) {
deferred.reject(err);
}
else {
deferred.resolve(result);
}
});
return deferred.promise;
}
In both cases, you can use callFunc like so
callFunc().then(function(res) {
console.log('success', res);
}, function(res) {
console.log('error', res);
});
Without specific details from you, that's the best I can offer
Here is a javascript code example of function with a callback.
function callFunc( param1, param2, callback ) {
//your codes of the function
//call the callback with some parameters
window[callback]( param1, param2 );
}
Example of calling the function
callFunc( 'this is param1', 'this is param2', 'call_this_function' );
Example of callback function mentioned in the function call
function call_this_function( param1, param2 ) {
//do something
}

Javascript When and Done Function use

I am trying to call two different functions in third function but one after the other.One function has ajax call, whose values are used in other function. it is step by step process. I don't want to use one into the other.
function f1()
{
// ajax call
return r1
}
function f2(r2)
{
// do some of the work based on r2
}
function f3()
{
$.when(f1()).done(function(data){
f2(data)
});
}
I also tried with $.when().then(); but still of no use.
Thanks, in advance.
UPDATE :- Below is the answer for my Question based on solution provide by #dreamweiver.
var json_data = '';
function f1()
{
$.ajax({
url: "test.php",
method: "POST",
async : false,
data: { },
success:function(data){
json_data = eval(data);
}
});
}
function f2(t)
{
console.log("values is "+t);
}
function f3()
{
$.when(f1()).done(function(){
f2(json_data);
});
}
Thanks everyone for your feedbacks.
Try this way, I have tested locally and it works perfectly
function deferredCalls () {
var jsonData = '';
var f1 = function ()
{
// ajax call
$.ajax({
url: "test.html",
method: "POST",
data: { id : menuId }
}).done(function(data) {
jsonData = data; //set the data
});
}
var f2 = function (data)
{
// do some of the work based on data
if(!!data){
//process the data
}
}
$.when(f1).done(function(){
f2(jsonData);
});
}
f1 function is called first which would in turn make a ajax request and return data on success, which is set to a function scope variable jsonData. Once this process is completed, f2 would be called which will start using jsonData, which is nothing but the data received from f1 function call.
This should be working:
function f1() {
// Or some other Ajax request that returns a promise
return $.getJSON('path/to/your/service');
}
function f2(r2) {
// ...
}
f1().done(f2);

javascript function inside jquery foreach loop doesn't wait for response

My foreach loop:
jQuery(".custom-checkbox").each(function() {
if (jQuery(this).attr('data-action') == 'true') {
if(deleteQuoteItemFromListing(jQuery(this).attr('data-id'))){
console.log('passed');
}else{
console.log('failed');
}
}
});
And the function is(It's using prototype) but it successes
function deleteQuoteItemFromListing(id){
//does someoperations and on success
delurl = getDelUrl()+id; //baseurl/module/action/delete/id
new Ajax.Request(delurl,{
method: 'get',
onSuccess: function(transport){
return TRUE;
}
})
}
but the problem is all foreach executes at once, and doesn't wait for response from function. It prints failed even the operation is success.
Updated
The other way round i tried first is this
jQuery('.delete-from-quote').click(function() {
var i = 0, j = 0;
jQuery(".custom-checkbox").each(function() {
if (jQuery(this).attr('data-action') == 'true') {
i++;
}
});
if (i == 0) {
alert('please choose product');
return false;
}
jQuery(".custom-checkbox").each(function() {
if (jQuery(this).attr('data-action') == 'true') {
var urlData = "<?php echo $this->getUrl('qquoteadv/index/delete/'); ?>";
urlData += "id/" + jQuery(this).attr('data-id') + "/"
var ajax = jQuery.ajax({
type: "GET",
url: urlData,
success: function(msg) {
j++;
}
})
}
if(i==j){location.reload();} //after completing all, reload the page
});
});
The problem is to know all action completed and reloading the page.
My guess is that the code you've omitted is doing an asynchronous ajax call. Since ajax is asynchronous by default, the code you write there ($.ajax or whatever) starts the process, but then the process continues in the background while your code continues to run.
There's no reasonable way to make the deleteQuoteItemFromListing function wait for the response. (While it's possible to do synchronous ajax, A) it makes for a poor user experience by locking up the browser UI, and B) jQuery will be removing that option at some stage, forcing you to go direct to XHR if you want to keep doing it.)
Instead, restructure your code to embrace the asynchronous nature of web programming by having your function either return a promise or accept a callback, and then resolve the promise or call the callback when done.
Here's a rough idea of what the promise version would look like:
jQuery(".custom-checkbox").each(function() {
if (jQuery(this).attr('data-action') == 'true') {
deleteQuoteItemFromListing(jQuery(this).attr('data-id'))
.done(function(id) {
console.log(id + ' passed');
})
.fail(function(id) {
console.log(id + ' failed');
});
}
});
function deleteQuoteItemFromListing(id){
var d = jQuery.Deferred();
jQuery.ajax(/*...*/)
.done(function() { // This bit assumes the deletion worked if
d.resolveWith(id); // the ajax call worked, and failed if the
}) // ajax call failed; if instead the ajax
.fail(function() { // call always works and returns a flag,
d.rejectWith(id); // adjust accordingly
});
return d.promise();
}
Using callback ensures that the function is executed.
jQuery(".custom-checkbox").each(function () {
if (jQuery(this).attr('data-action') == 'true') {
deleteQuoteItemFromListing(jQuery(this).attr('data-id'), handleData);
}
});
function handleData(data) {
if (data) {
console.log('passed');
} else {
console.log('failed');
}
}
function deleteQuoteItemFromListing(id, callback) {
//does someoperations and on success
delurl = getDelUrl() + id; //baseurl/module/action/delete/id
new Ajax.Request(delurl, {
method: 'get',
onSuccess: function (transport) {
callback(true);
}
})
}
I hope this will work for you. you need to define handleData function outside of the other function.
Use jquery When.
You need to queue those Deferred in an array of Deferred and then apply all of the functions at once.
If one fails all will fail and if all succeeds all will pass.
check this out jQuery When
var queue = [];
var items = 0;
return new $.Deferred(function (deferred) {
$(".custom-checkbox").each(function () {
if ($(this).attr('data-action') == 'true') {
items++;
queue.push(function () {
new Ajax.Request(delurl, {
method: 'get',
onSuccess: function (transport) {
items--;
if(items === 0)
deferred.resolve();
},
onError:function(e){
deferred.reject(e);
}
});
});
}
});
//now resolve all of the deferred fns
$.when(queue).done(function(){
console.log('All went well');
})
.fail(function(e){
console.log('Error ' + e);
});
});
(Part of) Your problem is in this simple statement:
return TRUE;
In JavaScript, the "true" boolean is written in lowercase:
return true;
The interpreter thinks TRUE is a variable, and will throw a ReferenceError, since it's not set / defined anywhere, meaning the function will never return true.

Return this in Object to create chain

I'm trying to write a small XHR abstraction as well as learn how to create chainable methods, I am nearly there (I think), but am at a loss as to what to do next, I think my setup is wrong.
What I want to do:
$http.get('file.txt')
.success(function () {
console.log('Success');
})
.error(function () {
console.log('Error');
});
What I've got:
window.$http = {};
$http.get = function (url, cb, data) {
var xhr = {
success: function (callback) {
callback();
return this;
},
error: function (callback) {
callback();
return this;
}
};
// just a test to call the success message
if (window) {
xhr.success.call(xhr);
}
return xhr;
};
I'm having trouble 'wiring' up the success/error messages, can anybody help point me in the right direction? Thanks in advance.
jsFiddle
Your chaining is OK, but you have a error at this line:
if (window) {
xhr.success.call(xhr); // Uncaught TypeError: undefined is not a function
}
So JavaScript breaks and doesn't return xhr. Delete thoses lines and it will work.
success and error are simply functions that store the passed functions into an internal storage. Once the XHR responds, your code should execute all callbacks accordingly, depending on the response status.
Now what you need is an object instance per request that stores its own set of success and error callbacks. Also, success and error methods should return the same instance to allow chaining.
This should set you to the right track:
(function (window) {
window.$http = {};
// An object constructor for your XHR object
function XHRObject(url,data){
// store the request data
this.url = url;
this.data = data;
// The callback storage
this.callbacks = {};
this.init();
}
// Methods
XHRObject.prototype = {
init : function(){
// Actual call here
// Depending on result, execute callbacks
var callbacksToExecute;
if(readyState === 4 && response.status === 200){
callbacksToExecute = this.callbacks.success;
} else {
callbacksToExecute = this.callbacks.error;
}
callbacksToExecute.forEach(function(callback){
callback.call(null);
});
},
success : function(cb){
// Create a success callback array and store the callback
if(this.callbacks.hasOwnProperty('success') this.callbacks.success = [];
this.callbacks.success.push(cb);
// You also need a flag to tell future callbacks to execute immediately
// if the current object has already responded
return this;
},
...
}
// A call to get basically returns an object
$http.get = function (url, data) {
return new XHRObject(url,data);
};
})(this);
I hope you can make something out of this:
window.$http = {};
$http.get = function (url, cb, data) {
var xhr = function(){
return {
success: function (callback) {
callback();
return this;
},
error: function (callback) {
callback();
return this;
}
};
};
return new xhr();
}
$http.get('url','cb','data')
.success(function () {
console.log('Success');
})
.error(function () {
console.log('Error');
});
Edit: I just realized this is basically the same code you wrote, except I'm missing the if(). It seems that test was causing the code to break.

Categories