This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 9 years ago.
This is a simplified code that runs on Node.js crawler and it gets all data.
But how do I insert inside the "callback": function value of var "i" from cycle for(var i=0... When I'm adding name: datas[i].name it returns an error:
TypeError: Cannot read property 'undefined' of undefined
var Crawler = require("crawler").Crawler;
var crawler = new Crawler;
var datas = [
{name: 'John', url: 'john025'},
{name: 'Jim', url: 'jim04567'}
];
function crauler(done) {
for (var i = 0; i < datas.length; i++) {
var link = 'http://somesite.com/' + datas[i].url;
crawler.queue([{
"uri": link,
// inside this func
"callback": function (error, result, $, datas, i) {
var arr = $('.blogpost').map(function (index) {
var str = $(this).attr('href');
var object = {
numb: str,
name: datas[i].name
};
return obj;
}).get().join(',');
done(arr);
} }]) }; };
crauler (function (arr) {
console.log(arr);
});
You can't pass datas and i into callback functions like this. What arguments that the callback functions will be called with are up to the caller, you don't have the control of it.
You're seeing "TypeError: Cannot read property 'undefined' of undefined" because you want your callback function to have datas and i as parameters; but the caller will call the callback with the first 3 arguments only [crawler callback reference], so the datas and i are undefined.
Therefore, you should remove the datas and i from in line:
"callback": function (error, result, $, datas, i) {
Because datas is defined in the outer scope of the callback function, the callback can access datas without any special treatment. For the variable i, it's a little bit tricky as mentioned in other answers, so you need to create a closure for it.
So, your callback function definition should be something looks like the following:
"callback": (function(i) { // create closure for i
return function (error, result, $) { // no more datas and i here
var arr = $('.blogpost').map(function (index) {
var str = $(this).attr('href');
var object = {
numb: str,
name: datas[i].name // access datas as it
};
return obj;
}).get().join(',');
done(arr);
}
})(i)
You're trying to create a closure around i inside of a loop which is causing you problems. This answer should help you:
JavaScript closure inside loops – simple practical example
You need a closure to capture the values, this is one way to solve the problem. Read up on closures.
Javascript
var Crawler = require("crawler").Crawler;
var crawler = new Crawler;
var datas = [{
name: 'John',
url: 'john025'
}, {
name: 'Jim',
url: 'jim04567'
}];
function queue(link, i) {
crawler.queue([{
"uri": link,
// inside this func
"callback": function (error, result, $, datas, i) {
var arr = $('.blogpost').map(function (index) {
var str = $(this).attr('href');
var object = {
numb: str,
name: datas[i].name
};
return obj;
}).get().join(',');
done(arr);
}
}]);
}
function crauler(done) {
for (var i = 0; i < datas.length; i++) {
var link = 'http://somesite.com/' + datas[i].url;
queue(link, i);
};
crauler(function (arr) {
console.log(arr);
});
Related
In my application, Asp web service returns JSON with object name called 'd' so I access that 'd' object the in the application as follows,
GetBranchOrRegionDataSourceSuccess: function (result, status, init) {
"use strict";
var regions = JSON.parse(result.d);
}
I called this function inside the Ajax success call.
Now the problem is I have Jquery function called searchLocations, Inside that function, I need to call this function. and need to pass the parameters.
I tried like this,
var jsonResult = JSON.stringify({'d':result});
this.GetBranchOrRegionDataSourceSuccess(jsonResult,"Success", true); //here I need to call the function
this is my complete function.
function searchName(prov,tree) {
var result = [];
let searchKey = new RegExp(prov, "i");
var objects = JSON.parse(json);
for (obj of objects) {
if (obj.Name.match(searchKey)) {
result.push(obj);
} else {
var toAdd = {"Id": obj.Id, "Name": obj.Name, "Branches": []};
for (branch of obj.Branches) {
if (branch.Name.match(searchKey)) {
toAdd.Branches.push(branch);
}
}
if (toAdd.Branches.length) {
result.push(toAdd);
}
}
}
var jsonResult = JSON.stringify({'d':result});
this.GetBranchOrRegionDataSourceSuccess(jsonResult,"Success", true); //here I need to call the function
}
But it makes error inside GetBranchOrRegionDataSourceSuccess on this line var regions = JSON.parse(result.d); How can I pass the result with 'd' object name
GetBranchOrRegionDataSourceSuccess is expecting an object with property d.
The d property value needs to be a JSON string (which it will parse).
You want
let jsonResult = { d: JSON.stringify(result) }
this.GetBranchOrRegionDataSourceSuccess(jsonResult, 'Success', true)
Thanks in advance for any responses:
I don't think this is a duplicate: I reviewed that article in the first comment, that is just a general breakdown of objects and using "this" within javascript.
My other this.function's perform just fine, so I at least have the basics of JS Obj's figured out.
This issue is related to using .map() with a this.function within a constructed object.
The following Google Appscript code uses .map() to update a string in a 2d array. [[string, int],[string, int]]
For some reason, when using .map() it is am unable to access the function "this.removeLeadingZero". If that same function is placed outside of the OBJ it can be called and everything works just fine. For some reason the system claims row[0] is an [object, Object] but when I typeof(row[0]) it returns "string" as it should.
Error: TypeError: Cannot find function removeLeadingZero in object [object Object]. (line 106, file "DEEP UPC MATCH")
Is there any issue using this.function's with .map() inside an object or am I using an incorrect syntax?
function test2DMapping(){
var tool = new WorkingMappingExample()
var boot = tool.arrayBuild();
Logger.log(boot)
}
function WorkingMappingExample(){
this.arr= [["01234", 100],["401234", 101],["012340", 13],["01234", 0422141],["01234", 2],["12340",3],["01234", 1],["01234", 2],["12340",3],["01234", 1],["01234", 2],["12340",3],["01234", 1],["01234", 2],["12340",3]];
//mapping appears faster that normal iterations
this.arrayBuild = function(){
var newArray1 =
this.arr.map( function( row ) {
**var mUPC = removeLeadingZero2(row[0])** //working
**var mUPC = this.removeLeadingZero(row[0])** // not working
var index = row[1]
Logger.log(mUPC + " " + index)
row = [mUPC, index]
return row
} )
return newArray1;
};
}; //end of OBJ
//THE NEXT 2 FUNCTIONS ARE WORKING OUTSIDE OF THE OBJECT
function removeLeadingZero2(upc){
try {
if (typeof(upc[0]) == "string"){
return upc.replace(/^0+/, '')
} else {
var stringer = upc.toString();
return stringer.replace(/^0+/, '')
}
} catch (err) {
Logger.log(err);
return upc;
}
}
function trimFirstTwoLastOne (upc) {
try {
return upc.substring(2, upc.length - 1); //takes off the first 2 #'s off and the last 1 #'s
} catch (err) {
Logger.log(err);
return upc;
}
}
Inside the function that you pass to map, this doesn't refer to what you think it does. The mapping function has its own this, which refers to window, normally:
var newArray1 = this.arr.map(function(row) {
// this === window
var mUPC = this.removeLeadingZero(row[0]);
var index = row[1];
Logger.log(mUPC + " " + index);
return [mUPC, index];
});
You have four options:
Array#map takes a thisArg which you can use to tell map what the this object in the function should be:
var newArray1 = this.arr.map(function(row) {
// this === (outer this)
var mUPC = this.removeLeadingZero(row[0]);
// ...
}, this); // pass a thisArg
Manually bind the function:
var newArray1 = this.arr.map(function(row) {
// this === (outer this)
var mUPC = this.removeLeadingZero(row[0]);
// ...
}.bind(this)); // bind the function to this
Store a reference to the outer this:
var self = this;
var newArray1 = this.arr.map(function(row) {
// self === (outer this)
var mUPC = self.removeLeadingZero(row[0]);
// ...
});
Use an arrow function:
var newArray1 = this.arr.map(row => {
// this === (outer this)
var mUPC = this.removeLeadingZero(row[0]);
// ...
});
Additionally, you could stop using this and new.
I have solved this issue and below is the answer in case anyone else runs into this:
this needs to be placed into a variable:
var _this = this;
and then you can call it within the object:
var mUPC = _this.removeLeadingZero(row[0])
Javascript scope strikes again!
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 7 years ago.
I'm trying to build a function that stores an array of JS objects in a global scope (I want to access this from an external Prototype function). However, when I try to return the 'build' array, the array is undefined (this is probally because I need a proper callback function).
How can I achieve this in a proper way?
function getMyJson(url){
var request = $.getJSON(url);
var items = [];
request.done(function(response) {
for (var key in response) {
if (response.hasOwnProperty(key)) {
var object = {
name: response[key].name,
id: response[key].id
}
items.push(object);
}
}
});
return items; // This returns 'undefined', probally because the for loop is still running
}
var data = getMyJson('data.json');
console.log(data); // undefined
Thanks in advance
As others have mentioned, callbacks are the way to go.
function getMyJson(url, callback){
var request = $.getJSON(url);
var items = [];
request.done(function(response) {
for (var key in response) {
if (response.hasOwnProperty(key)) {
var object = {
name: response[key].name,
id: response[key].id
}
items.push(object);
}
}
callback(items);
});
}
var data = getMyJson('data.json', function(items){
//items will be defined here.
});
I have a bunch of functions that look like this
'tea' : function (caller) {
this.speak(this.randomOption('tea_msg'));
},
'chill' : function (caller) {
this.speak(this.randomOption('chill_msg'));
},
and what i want is to merge them to something like this
'tea' ,'chill': function (caller) {
// get which function name was called in var lets call it fun
this.speak(this.randomOption(fun +'_msg'));
}
Is this possible or am I going about this the wrong way?
You can make a function-maker-function that would get pretty close
function speaker(message){ return function(caller){
this.speak(this.randomOption(message + '_msg');
};}
{
'tea': speaker('tea'),
'chill': speaker('chill')
}
And if you want to avoid retyping the 'tea' and 'chill' you can use the [] object-subscripting syntax together with some sort of loop:
var funs = {};
var msgs = ['tea', 'chill'];
for(var i=0; i<msgs.length; i++){
var msg = msgs[i];
funs[msg] = speaker(msg);
}
Just be careful about the closures-inside-for-loops if you want to write the speaker function inline instead.
[ 'tea', 'chill', 'elephant', 'moose' ].forEach(function (val) {
//creating a closure so `val` is saved
(function (val) {
obj[ val ] = function ( caller ) {
this.speak( this.randomOption(val + '_msg') );
};
}(val));
});
You can also write a generic say function:
say : function ( caller, type ) {
this.speak( this.randomOption(type + '_msg') );
}
You could pass the function name as a variable?
'text': function (caller, functionName) {
// get which function name was called in var lets call it fun
this.speak(this.randomOption(functionName +'_msg'));
}
I'm trying to generate an array of callback functions for use in a jQuery UI dialog
Given the following code:
for(var x in methods)
{
buttons[x] = function() {
var method = methods[x];
var data = $('#dialog_'+model+' form').serialize();
data += '&form='+model;
$.post(
$('#dialog_'+model+' form').attr('action')+'method/'+method+'/',
data,
function(r) {
handleFormReturn(r);
},
'json'
);
};
}
When called, the function will obviously use the last known value of the variable x and not the one that I need. How can I avoid this problem without having to resort to using eval() ?
Maybe I'm going about this all wrong but as far as I know it's not possible to pass a parameter to the callback.
You need to create a new variable scope during each pass in the for loop. This can only be done by invoking a function.
function createButton(x) {
buttons[x] = function () {
var method = methods[x];
var data = $('#dialog_' + model + ' form').serialize();
data += '&form=' + model;
$.post(
$('#dialog_' + model + ' form').attr('action') + 'method/' + method + '/', data, function (r) {
handleFormReturn(r);
}, 'json');
};
}
for (var x in methods) {
createButton(x);
}
Now the value of x that the buttons[x] function refers to will be the one that was passed to createButton.
An immediate function version of patrick dw's solution:
for (var x in methods) {
buttons[x] = (function (x) {
return function () {
/* ... x is local for this function ... */
};
})(x);
}
You need to create a closure for each element in methods array:
for(var x in methods) {
buttons[x] = (function(x) {
var method = methods[x];
return function () {
var data = $('#dialog_'+model+' form').serialize();
data += '&form='+model;
$.post(
$('#dialog_'+model+' form').attr('action')+'method/'+method+'/',
data,
function(r) {
handleFormReturn(r);
},
'json'
);
};
})(x);
}