Scope of a callback function - javascript

I want to implement a function which performs ajax requests (response is json) until there will be no "next" property in response. After that i need to perform some callback function. What is the best way for this? My code below doesn't work because of the wrong callback's scope, and i cannot imagine how to pass it correctly.
_requestPhotos = function(url, callback) {
getYFContent(url, function(data) {
// some actions
if (!!data.next) {
_requestPhotos(data.next, callback);
} else {
callback(smth);
}
});
};

There are no obvious errors from the script you've posted. For example, an equivalent test could look like this:
alertResult = function(text) {
console.log("Result is: " + text);
}
doRecursive = function(data, callback) {
if(!!data.next) {
doRecursive(data.next, callback);
} else {
callback(data.value);
}
}
var d = { value: 1, next: { value: 2, next: { value: 3 }}};
doRecursive(d, alertResult);
The log result is "Result is: 3", which is what you'd expect.
The error is elsewhere. How are you calling this the first time, what is the callback you're passing to it (and how is it defined) and what exactly does getYFContent do?

Related

Passing callback function in NodeJS turns into Object

I am making an Alexa skill with AWS Lambda functions in NodeJS.
The app is throwing error when I call an Intent:
"errorMessage": "Exception: TypeError: object is not a function"
First, my app gets an event. If it's an Intent, it calls:
exports.handler = function (event, context) {
try {
...
else if (event.request.type === "IntentRequest") {
onIntent(
event.request,
event.session,
function intent_callback(sessionAttributes, speechletResponse) {
context.succeed(buildResponse(sessionAttributes, speechletResponse));
}
);
You can see the above passes a callback to onIntent(). It checks which Intent it is. Console.logging here shows the passed through callback as a function:
function onIntent(intentRequest, session, callback) {
if ("ItemIntent" === intentName) {
console.log(callback); // This is a function
getOrderResponse(intent, session, callback);
Yet, the type of the callback in getOrderResponse() somehow turns into an object? This would be why I'm getting that error, but I don't see how it's not a function type here. Why is it an object?
function getOrderResponse(callback) {
console.log('getOrderResponse', callback); // type = Object: { name: 'ItemIntent', slots: { Item: { name: 'Item' } } }
var card_title = config.data().CARD_TITLE;
var sessionAttributes = {},
speechOutput = 'So you want quick order',
shouldEndSession = false,
repromptText = 'Hello';
sessionAttributes = {
'speechOutput': repromptText,
'repromptText': repromptText,
'questions': 'some questions'
};
callback(sessionAttributes, buildSpeechletResponse(card_title, speechOutput, repromptText, shouldEndSession));
}
The callback will have to be the third parameter.
getOrderResponse(intent, session, callback); The first parameter you are sending is the the intent object.
function getOrderResponse(callback) {
should be
function getOrderResponse(intent, session, callback) {

nodejs, mysql, async itterative functions

I'm having a bit of trouble with an itterative function in nodejs.
I'm stepping through an object and checking if that object has any sub-objects attached (think: a star has a planet has a moon has an orbital station has a ship).
I'm trying to assemble this all into a nice array of objects to push to the client.
Here's the function:
var subNodeProc = function(nodeList,sqlP,itteration_count) {
var async = require('async');
--itteration_count;
async.each(nodeList,function(dd,cb){
var simple = {
sql:sqlP,
values:[dd.node_id],
timeout:40000
};
dd.subnodes = false;
connection.query(simple, function(err,rslt){
if (err) {
cb(err);
} else {
if (rslt.length > 0) {
var r = nodeList.indexOf(dd);
if (itteration_count > 0) {
rslt = subNodeProc(rslt,sqlP,itteration_count);
}
nodeList[r].subnodes = rslt;
}
cb();
}
});
},function(err){
if (err) {
return err;
} else {
return nodeList;
}
});
}
When I trigger the function it returns a nodelist of undefined. Can anyone give me a pointer in the right direction? I can't get it to work
Thanks!
Edit: here's a sample of the data I'm itterating over:
The SQL statement:
SELECT n.id as node_id, n.name, n.system_id, n.parent_id as parent_id FROM nodes as n WHERE n.parent_id = ?
Sample nodeList for input:
[ { node_id: 1,
name: 'Planet A',
system_id: 1,
parent_id: null,
},
{ node_id: 2,
name: 'Moon',
system_id: 1,
parent_id: 1,
},
{ node_id: 3,
name: 'Debris',
system_id: 1,
parent_id: 2,
},
{ node_id: 4,
name: 'Asteroid',
system_id: 1,
parent_id: 1,
} ]
Moon A has a parent_id of 1 and node_id of 2, moon A also has a ship (ship A, node_id:3, parent_id:2) orbiting it.
What I want :
[ { node_id: 1,
name: 'Planet A',
system_id: 1,
parent_id: null,
subnodes:[{
node_id: 2,
name: 'Moon A',
system_id: 1,
parent_id: 1,
subnodes: [{
node_id:3,
name: 'Ship A',
system_id:1,
parent_id:2
},
{...}]
},
{...}]
},
{...}]
It's hard to tell whether there are any other major issues because I cannot see the data which you are feeding the method. However, there is one major problem with this: you're attempting to return data from a method that uses asynchronous method calls.
The asynchronous way is to return values via a callback. In your code, the very last function in your example (the callback) is called from a completely different scope (from within the async framework) so your nodeList or err is being lost in a scope you don't control.
You need to rethink your code so that the returned data is passed to a callback. You could leverage the async callback for this. Ad a callback argument to your subNodeProc method. Then you can call that callback, after async has finished, passing it the nodeList:
var subNodeProc = function (nodeList, sqlP, itteration_count, cb) {
var async = require('async');
--itteration_count;
async.each(nodeList,function(dd, cb){
var simple = {
sql:sqlP,
values:[dd.node_id],
timeout:40000
};
dd.subnodes = false;
connection.query(simple, function(err, rslt){
if (err) {
cb(err);
} else {
if (rslt.length > 0) {
var r = nodeList.indexOf(dd);
if (itteration_count > 0) {
rslt = subNodeProc(rslt,sqlP,itteration_count);
}
nodeList[r].subnodes = rslt;
}
cb();
}
});
}, function (err) {
if (err)
throw err;
else
cb(nodeList);
});
}
You would then use the method like this:
subNodeProc(nodeList, sqlP, itteration_count, function (processed) {
console.log(processed);
/* do whatever you want afterwards here */
});
Okay, the solution was pretty obvious, once I sussed it out. Much thanks to #shennan for getting me going.
The key is:
as #shennan mentioned, you don't work with returns, as we're working asynchronously. This means callbacks
and
You have to trigger callbacks for each part of the function. This is not possible with just one function, so to get the objects returning you need two, each doing different parts of the original function.
Here's what I've come up with. Hope someone can look it over and give me an opinion...
// Main processing function.
var subNodeProc = function(nodeList,sqlP,itteration_count,cback) {
var async = require('async');
itteration_count--;
async.each(nodeList,function(dd,cb){
if (itteration_count > 0) {
// Trigger SQL Walker subNodeProcWalker with the necessary data (dd.node_id in this case, with a callback)
subNodeProcWalker(dd.node_id,sqlP,itteration_count,function(nl) {
// Hey look! the walker has done its business. Time to fill the subnode and tell async we're done with this array node.
dd.subnodes = nl;
cb();
});
}
},function(){
// At the end of the run, return the nodelist intact.
cback(nodeList);
});
}
// SQL Walker with callback for subNodeProc
var subNodeProcWalker = function(node_id,sqlP,itteration_count,cback){
// assemble the object for the query and do the query
var simple = {
sql:sqlP,
values:[node_id],
timeout:40000
};
connection.query(simple, function(err,rslt){
if (err) {
console.log('Error in Query');
console.log(simple);
console.log(err);
cback(false);
} else {
// no error and a result? Quick! Trigger subNodeProc again
if (rslt.length > 0) {
subNodeProc(rslt,sqlP,itteration_count,function(nodePol) {
// Lookie lookie! A result from subNodeProc! There's life there! Quick! tell this function we're done!
cback(nodePol);
});
} else {
cback(false);
}
}
});
}

Callbacks not called in Azure mobile services shared scripts

After refactoring some of our scripts and putting some functions in separate custom modules in shared folder, I had encountered a very strange problem. Don't even know how to describe it properly. Think best way is to show it by an example. I apologise in advance, for long portions of code I will post here. I just don't know how else I can explain my problem.
We have several shared scripts with some of our common functional:
shared/commons-util.js - several convenient utility functions, among them is this one:
function getErrorHandler(errMessage, callback) {
return function(err) {
console.log('inside error handler')
console.error(errMessage + '\nCause: ', err)
if (callback instanceof Function) {
callback(err)
}
}
}
exports.getErrorHandler = getErrorHandler
shared/commons-db.js - helper functions for working with database tables:
var waitingTime = +process.env.throttlingWaitTime || 0;
var retryCount = +process.env.throttlingRetryCount || 0;
function refreshThrottlingLimit() {
var throttlingLimit = +process.env.throttlingLimit || 0
if (LMD_GLOBALS.throttling.limit != throttlingLimit) {
LMD_GLOBALS.throttling.available += throttlingLimit - LMD_GLOBALS.throttling.limit
LMD_GLOBALS.throttling.limit = throttlingLimit
}
}
function throttledWrite(operation, table, data, callbacks, retriesLeft) {
refreshThrottlingLimit()
if (LMD_GLOBALS.throttling.limit && LMD_GLOBALS.throttling.available > 0) {
LMD_GLOBALS.throttling.available--
try {
console.log('Executing ' + operation + ' on table: ' + table.getTableName() + ', data: ', data, 'callbacks: ', callbacks)
table[operation](data, {
success: function(result) {
try {
console.log('throttledWrite: SUCCESS')
LMD_GLOBALS.throttling.available++
if (callbacks && callbacks.success) callbacks.success(result)
} catch (e) {
console.error('Exception in success callback', e)
}
},
error: function(err) {
try {
console.log('throttledWrite: ERROR')
LMD_GLOBALS.throttling.available++
err.table = table.getTableName()
err.operation = operation
err.data = data
if (callbacks && callbacks.error) callbacks.error(err)
} catch (e) {
console.error('Exception in error callback', e)
}
}
})
console.log(operation + ' started...')
} catch (e) {
console.error('Exception starting ' + operation, e)
}
} else if (retriesLeft > 0) {
setTimeout(throttledWrite, waitingTime, operation, table, data, callbacks, retriesLeft - 1)
} else {
if (callbacks && callbacks.error) callbacks.error(new Error('Aborting ' + operation + ' operation (waited for too long)'))
}
}
exports.throttledInsert = function(table, data, callbacks) {
throttledWrite('insert', table, data, callbacks, retryCount)
}
exports.throttledUpdate = function(table, data, callbacks) {
throttledWrite('update', table, data, callbacks, retryCount)
}
shared/commons.js - Just combining this two modules:
exports.db = require('../shared/commons-db.js')
exports.util = require('../shared/commons-util.js')
We are using this modules in scheduled script executeBackgroundJobs.js:
var commons = require('../shared/commons.js');
// Same high-order function as in `commons-util.js`. Only difference is that it is defined locally, not in the separate module.
function getErrorHandler(errMessage, callback) {
return function(err) {
console.log('inside error handler')
console.error(errMessage + '\nCause: ', err)
if (callback instanceof Function) {
callback(err)
}
}
}
function executeBackgroundJobs() {
test();
}
function test() {
console.log('Testing paranormal activity in Azure');
var testTable = tables.getTable('test');
var callback = function(res) {
console.log('Test OK', res)
}
var errMessage = 'Could not write to `test` table';
var errHandler_1 = commons.util.getErrorHandler(errMessage, callback); // First handler we get from high-order function from custom module
var errHandler_2 = getErrorHandler(errMessage, callback); // Second handler must be the same, just getting from local function not from the external module
// This log lines just show that this two functions are identical
console.log('errHandler_1: ', errHandler_1);
console.log('errHandler_2: ', errHandler_2);
// We are calling `throttledUpdate` two times with two different error handlers for each invocation
commons.db.throttledUpdate(testTable, {
id: 'test-01', // Data object is intentionally illegal, we want update to fail
someColumn: 'some value #1'
}, {
success: callback,
error: errHandler_1
});
commons.db.throttledUpdate(testTable, {
id: 'test-02', // Data object is intentionally illegal, we want update to fail
someColumn: 'some value #2'
}, {
success: callback,
error: errHandler_2
});
}
Now here's the output of the one invocation of this job:
1: First call of throttledWrite:
INFORMATION:
Executing update on table: test, data: { id: 'test-01', someColumn: 'some value #1' } callbacks: { success: [Function], error: [Function] }
2: Start of the test(). It is already strange that logs are not in proper order, cause this functions so far are called synchronously (or at least I believe so). Does this mean that logging in Azure is happening in different threads? Anyway, this is not our main problem.
INFORMATION:
Testing paranormal activity in Azure
3: Here we just show the body of first handler
INFORMATION:
errHandler_1: function (err) {
console.log('inside error handler')
console.error(errMessage + '\nCause: ', err)
if (callback instanceof Function) {
callback(err)
}
}
4: Here we just show the body of second handler
INFORMATION:
errHandler_2: function (err) {
console.log('inside error handler')
console.error(errMessage + '\nCause: ', err)
if (callback instanceof Function) {
callback(err)
}
}
5: table.update method was launched successfully in the first throttledWrite (no exceptions)
INFORMATION:
update started...
6: Second call of throttledWrite:
INFORMATION:
Executing update on table: test, data: { id: 'test-02', someColumn: 'some value #2' } callbacks: { success: [Function], error: [Function] }
7: table.update method was launched successfully in the second throttledWrite (no exceptions)
INFORMATION:
update started...
9: Error handler was called
INFORMATION:
inside error handler
10: From only the second throttledWrite
ERROR:
Could not write to `test` table
Cause: { _super: undefined,
message: 'Could not save item because it contains a column that is not in the table schema.',
code: 'BadInput',
table: 'test',
operation: 'update',
data: { id: 'test-02', someColumn: 'some value #2' } }
11: And that's all
INFORMATION:
Test OK { _super: undefined,
message: 'Could not save item because it contains a column that is not in the table schema.',
code: 'BadInput',
table: 'test',
operation: 'update',
data: { id: 'test-02', someColumn: 'some value #2' } }
First of all the strangest thing I cannot understand is why there was no callback invoked in first throttledWrite call.
And besides that, even in the second throttledWrite, where callback was called, no log was written from table.update-s error callback. Remember this lines from throttledWrite function:
error: function(err) {
try {
console.log('throttledWrite: ERROR')
LMD_GLOBALS.throttling.available++
err.table = table.getTableName()
err.operation = operation
err.data = data
if (callbacks && callbacks.error) callbacks.error(err)
} catch (e) {
console.error('Exception in error callback', e)
}
}
I have to add that this is not the actual code where I first encountered this problems. I tried to shorten code before posting. So you may don't fully understand the purposes of some methods. It does not matter, this code is just for demonstration.
That's it. I have three mysteries that I cannot explain:
Why callbacks are not called (in first case)?
Why did console.log('throttledWrite: ERROR') not write anything in log (when I am sure, it was called and was successful)?
Less critical, but still interesting, why are logs not in proper order?

Jquery Deferred. Multiple ajax calls. deferred vs async false

I've got the following.
var lookupInit = function () {
http.get('api/employmenttype', null, false)
.done(function (response) {
console.log('loaded: employmenttype');
vm.lookups.allEmploymentTypes(response);
});
http.get('api/actionlist', null, false)
.done(function (response) {
console.log('loaded: actionlist');
vm.lookups.allActionListOptions(response);
});
http.get('api/company', null, false)
.done(function (response) {
console.log('loaded: company');
vm.lookups.allCompanies(response);
});
//... x 5 more
return true;
};
// somewhere else
if (lookupInit(id)) {
vm.userInfo.BusinessUnitID('0');
vm.userInfo.BuildingCode('0');
if (id === undefined) {
console.log('api/adimport: latest');
http.json('api/adimport', { by: "latest" }, false).done(viewInit);
}
else if (id !== undefined) {
console.log('api/adimport: transaction');
http.json('api/adimport', { by: "transaction", TransactionId: id }, false).done(viewInit);
}
} else {
console.log('User info init failed!');
}
The following "http.get('api/employmenttype', null, false)" means i set async to false.
I'm aware that this is probably inefficient. And i'd like to have all the calls load simultaneously.
The only problem is if i don't have them set to async false, the second part of my code might execute before the dropdowns are populated.
I've tried a couple of attempts with Jquery Deferreds, but they have resulted in what i can only describe as an abortion.
The only thing i'm looking to achieve is that the lookup calls finish before the adimport/second part of my code, in any order.... But having each call wait for the one before it to finish EG: async, seems like the only solution I'm capable of implementing decently ATM.
Would this be an appropriate place for deferred function, and could anyone point me into a direction where i could figure out how to implement it correctly, as I've never done this before?
You can use $.when to combine multiple promises to one that resolves when all of them have been fulfilled. If I got you correctly, you want
function lookupInit() {
return $.when(
http.get('api/employmenttype').done(function (response) {
console.log('loaded: employmenttype');
vm.lookups.allEmploymentTypes(response);
}),
http.get('api/actionlist').done(function (response) {
console.log('loaded: actionlist');
vm.lookups.allActionListOptions(response);
}),
http.get('api/company').done(function (response) {
console.log('loaded: company');
vm.lookups.allCompanies(response);
}),
// … some more
);
}
Then somewhere else
lookupInit(id).then(function(/* all responses if you needed them */) {
vm.userInfo.BusinessUnitID('0');
vm.userInfo.BuildingCode('0');
if (id === undefined) {
console.log('api/adimport: latest');
return http.json('api/adimport', {by:"latest"})
} else {
console.log('api/adimport: transaction');
return http.json('api/adimport', {by:"transaction", TransactionId:id});
}
}, function(err) {
console.log('User info init failed!');
}).done(viewInit);
In the Jquery API I've found this about resolving multiple deferreds:
$.when($.ajax("/page1.php"), $.ajax("/page2.php")).done(function(a1, a2){
/* a1 and a2 are arguments resolved for the
page1 and page2 ajax requests, respectively.
each argument is an array with the following
structure: [ data, statusText, jqXHR ] */
var data = a1[0] + a2[0]; /* a1[0] = "Whip", a2[0] = " It" */
if ( /Whip It/.test(data) ) {
alert("We got what we came for!");
}
});
Using this with your code:
var defer = $.when(
$.get('api/employmenttype'),
$.get('api/actionlist'),
$.get('api/company'),
// ... 5 more
);
defer.done(function (arg1, arg2, arg3 /*, ... 5 more*/) {
vm.lookups.allEmploymentTypes(arg1[0]);
vm.lookups.allEmploymentTypes(arg2[0]);
vm.lookups.allEmploymentTypes(arg3[0]);
// .. 5 more
vm.userInfo.BusinessUnitID('0');
vm.userInfo.BuildingCode('0');
if (id === undefined) {
console.log('api/adimport: latest');
http.json('api/adimport', { by: "latest" }, false).done(viewInit);
} else if (id !== undefined) {
console.log('api/adimport: transaction');
http.json('api/adimport', { by: "transaction", TransactionId: id }, false).done(viewInit);
}
});
You can use the defer of the $.when() inside an other $.when(), so if the json calls are not dependant on the first calls you can add them in a an onther defer.

Get object value

I have an object
var actions = {
'photos': function()
{
var self = this; // self = actions
$.get('./data.php?get=photos', function(data)
{
self.result = data;
});
},
'videos': function()
{
var self = this;
$.get('./data.php?get=videos', function(data)
{
self.result = data;
});
}
};
Each function creates one more item in actions called result
Then, instead of switch I use this (works good):
if (actions[action])
{
actions[action](); // call a function
console.log(actions);
console.log(actions.result);
}
action is a variable with value photos or videos.
console.log(actions) gives this:
Object
message: function ()
messages: function ()
profile: function ()
profile-edit: function ()
result: "<div>...</div>"
__proto__: Object
So I think there is resultitem in actions with the value "<div>...</div>".
But, console.log(actions.result) returns undefined.
Why?
I know all this code may be rewrited, but I would like to understand the reason of undefined.
Because we are dealing with asynchronous requests, we use "callbacks".
A callback is called when an asynchronous request is ready. Your request will get a response, and you send that response with the callback. The callback handles the response.
var actions = {
'photos': function(callback)
{
$.get('./data.php?get=photos', callback);
},
'videos': function(callback)
{
$.get('./data.php?get=videos', callback);
}
};
var action = 'photos';
actions[action](function(data) {
console.log(data);
});
Since you ensist on keeping the values, I would use this structure:
var actions = {
'photos': function()
{
$.get('./data.php?get=photos', function() {
this.__callback('photos', data);
});
},
'videos': function()
{
$.get('./data.php?get=videos', function() {
this.__callback('videos', data);
});
},
'__callback': function(action, data) {
this.results[action].push(data);
},
'results': {
'photos': [],
'videos': []
}
};
var action = 'photos';
actions[action]();
// use a timeout because we are dealing with async requests
setTimeout(function() {
console.log(actions.results); // shows all results
console.log(actions.results.photos); // shows all photos results
console.log(actions.results.videos); // shows all videos results
}, 3000);
gaaah what a horrible piece of code...

Categories