javascript closures getting variable out and wrong context [duplicate] - javascript

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 7 years ago.
what is correct way of writing this in js?
notice this.signals.total is in a wrong context.
articleSchema.pre('save', function(next) {
if (!this.publisher) {
this.publisher = this.url;
}
social.facebook(this.url, function(err, signals) {
//problem is this
this.signals.total = signals.total_count;
});
if (!this.weight) {
this.weight = 1440;
}
var currentDate = new Date();
this.updated_at = currentDate;
if (!this.created_at) {
this.created_at = currentDate;
}
next();
});
this in that case refers to social.facebook correct?
There are several ways I could deal with the problem, e.g. create outside variable, but what is js way?

A simple solution is to store a reference to the correct context:
articleSchema.pre('save', function(next) {
// ...
var that = this; // Store a reference to `this`.
social.facebook(this.url, function(err, signals) {
// Use `that`.
that.signals.total = signals.total_count;
});
// ...
});

Yes, a common solution is to assign the this pointer to a variable outside the inner function like this:
var self = this;
function foo() {
// this would not work, but self does
}
In ECMA-Script 6, you can also use an arrow function instead.
social.facebook(this.url, (err, signals) => {
// this will point to the outer this
});
See this article for more details:

It is totally depends on how the callback is called.
1/ this will be global.
social.facebook = function(url, fn) {
fn(url)
}
2/ this will be social
social.facebook = function(url, fn) {
fn.call(this, url)
}
This way this can also be something weird:
social.facebook = function(url, fn) {
fn.call(weirdObject, url)
}
Create outside variable like the others answer is one way, the other one is bind.
social.facebook(this.url, function(err, signals) {
//problem is this
this.signals.total = signals.total_count;
}.bind(this));

Related

"this" return undefined in a prototype function [duplicate]

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 6 years ago.
I want to run multiples asynchronous task with async package.
But I have issues with javascript prototypes access.
Here's the sample of my code:
var env = function(options) {
this.options = options;
}
env.prototype.run = function() {
var self = this,
async.series([
self.task1,
self.task2
], function done(responses) {
console.log(responses);
});
}
env.prototype.task1 = function() {
console.log(this.options); // undefined
// logic code...
}
var foo = new env({foo: 'bar'});
foo.run(); // undefined - from console.log
Don't know why I can't access the 'this' properties of my Object
This code
async.series([
self.task1,
self.task2
], function done(responses) {
console.log(responses);
});
just passes function references into async.series, but does nothing to ensure that this is correct when they're called.
Unless the async.series you're using offers a way to tell it what this to use, you can readily solve the problem with bind:
async.series([
self.task1.bind(self), // ***
self.task2.bind(self) // ***
], function done(responses) {
console.log(responses);
});
Here's a simpler example demonstrating the problem and solution:
var obj = {
property: "testing",
wrong: function() {
setTimeout(this.task1, 10);
},
right: function() {
setTimeout(this.task1.bind(this), 20);
},
task1: function() {
console.log("task1 says the property is " + this.property);
}
};
obj.wrong();
obj.right();
Side note: Unless you're using self for something you haven't shown inside run, you don't need it at all:
env.prototype.run = function() {
async.series([
this.task1.bind(this),
this.task2.bind(this)
], function done(responses) {
console.log(responses);
});
};
Another option if you're using an ES2015-compatible environment (or transpiling) is to wrap your tasks in arrow functions:
// Requres ES2015 support
env.prototype.run = function() {
async.series([
(...args) => this.task1(...args),
(...args) => this.task2(...args)
], function done(responses) {
console.log(responses);
});
};

How to add extra parameters to a function callback

I am using a few callbacks in an app that I'm writing. I am using Mongoose models and need to save a few different places. The save function takes a callback, and the callback gets error and model for its parameters, but I'd like to send the callback an extra parameter that the function needs. I'm not sure of the proper syntax to be able to do this. Below is some example code of what I'm going for...
var saveCallBack = function(err, model, email_address){
if(err) {
//handle error
}
else {
//use the third parameter, email_address, to do something useful
}
};
Below, token is a mongoose model. As I said, save takes a callback and gets passed error and model, but I'd like to also send my callback a variable email_address that I figure out at some other point. Obviously the appendParameter function is pseudo-code, but this is the type of functionality that I need.
token.save(saveCallBack.appendParameter(email_address));
If you make that the first parameter instead, you can use .bind().
token.save(saveCallBack.bind(null, email_address));
var saveCallBack = function(email_address, err, model){};
I'm using bind function for appending additional parameters for callbackes
var customBind = function (fn, scope, args, appendArgs) {
if (arguments.length === 2) {
return function () {
return fn.apply(scope, arguments);
};
}
var method = fn,
slice = Array.prototype.slice;
return function () {
var callArgs = args || arguments;
if (appendArgs === true) {
callArgs = slice.call(arguments, 0);
callArgs = callArgs.concat(args);
} else if (typeof appendArgs == 'number') {
callArgs = slice.call(arguments, 0);
}
return method.apply(scope || window, callArgs);
};
}
This customBind function accepts four arguments, first one is original callback function, second is the scope, third is additional parameters (array), and fourth is flag append or replace. If you set last parameter to false than only parameters in array will be available in this function.
and with this function you can simple add new parameters or to override the existing one
var callback = customBind(saveCallBack, this, [array_of_additional_params], true)
in this way all original parameters remain and your parameter will be appended to the end.
No matter how many parameter you defined, the callee will always pass the same parameter inside its process.
but it will be more simple, just use a variable that is visible from outside of the callback.
Eg:
var email = 'yourmail#mail.com';
var saveCallBack = function(err, model){
if(err) {
//handle error
}
else {
alert(email);
}
};
Updated (#Jason): then you can use Immediately-Invoked Function Expression (IIFE)
(function(mail){
var saveCallBack = function(err, model){
if(err) {
//handle error
}
else {
alert(mail);
}
};
token.save(saveCallBack);
}, emailAddress);

Mongoose passing class functions

When I pass functions to mongoose, it seems it no longer has a reference to this. Is there a better way to go about this? All functions are simplified for length reasons. I cannot edit the function getUsernameForId to take additional parameters.
I have class:
var class = new function() {
this.func1 = function(data) {
return data + "test";
}
this.func2 = function(data) {
var next = function(username) {
return this.func1(username); // THIS THROWS undefined is not a function
}
mongoose.getUsernameForId(1, func3);
}
}
mongoose is another class like this:
var getUsernameForId = function(id, callback) {
user_model.findOne({"id": id}, function(err, user) {
if(err) {
throw err;
}
callback(user.username);
});
}
How do I resolve the undefined is not a function error. I do not want to duplicate code because func1 is pretty long in reality.
It's not clear from your code how next is used, but if you need it to be invoked with correct this you can try to use Function.prototype.bind method:
this.func2 = function(data) {
var next = function(username) {
return this.func1(username);
}.bind(this);
mongoose.getUsernameForId(1, func3);
}
I assume that you simplified code for the post and next does more things in reality. But if it indeed just returns result of this.func1 then you could shorten it:
var next = this.func1.bind(this);

Wrapping a function in Javascript / jQuery

If I have an arbitrary function myFunc, what I'm aiming to do is replace this function with a wrapped call that runs code before and after it executes, e.g.
// note: psuedo-javascript
var beforeExecute = function() { ... }
var afterExecute = function() { ... }
myFunc = wrap(myFunc, beforeExecute, afterExecute);
However, I don't have an implementation of the required wrap function. Is there anything that already exists in jQuery like this (I've had a good look through the docs but cannot see anything)? Alternatively does anybody know of a good implementation of this because I suspect that there are a bunch of edge cases that I'll miss if I try to write it myself?
(BTW - the reason for this is to do some automatic instrumentation of functions because we do a lot of work on closed devices where Javascript profilers etc. are not available. If there's a better way than this then I'd appreciate answers along those lines too.)
Here’s a wrap function which will call the before and after functions with the exact same arguments and, if supplied, the same value for this:
var wrap = function (functionToWrap, before, after, thisObject) {
return function () {
var args = Array.prototype.slice.call(arguments),
result;
if (before) before.apply(thisObject || this, args);
result = functionToWrap.apply(thisObject || this, args);
if (after) after.apply(thisObject || this, args);
return result;
};
};
myFunc = wrap(myFunc, beforeExecute, afterExecute);
The accepted implementation does not provide an option to call wrapped (original) function conditionally.
Here is a better way to wrap and unwrap a method:
/*
Replaces sMethodName method of oContext with a function which calls the wrapper
with it's list of parameters prepended by a reference to wrapped (original) function.
This provides convenience of allowing conditional calls of the
original function within the wrapper,
unlike a common implementation that supplies "before" and "after"
cross cutting concerns as two separate methods.
wrap() stores a reference to original (unwrapped) function for
subsequent unwrap() calls.
Example:
=========================================
var o = {
test: function(sText) { return sText; }
}
wrap('test', o, function(fOriginal, sText) {
return 'before ' + fOriginal(sText) + ' after';
});
o.test('mytext') // returns: "before mytext after"
unwrap('test', o);
o.test('mytext') // returns: "mytext"
=========================================
*/
function wrap(sMethodName, oContext, fWrapper, oWrapperContext) {
var fOriginal = oContext[sMethodName];
oContext[sMethodName] = function () {
var a = Array.prototype.slice.call(arguments);
a.unshift(fOriginal.bind(oContext));
return fWrapper.apply(oWrapperContext || oContext, a);
};
oContext[sMethodName].unwrapped = fOriginal;
};
/*
Reverts method sMethodName of oContext to reference original function,
the way it was before wrap() call
*/
function unwrap(sMethodName, oContext) {
if (typeof oContext[sMethodName] == 'function') {
oContext[sMethodName] = oContext[sMethodName].unwrapped;
}
};
This is the example I would use
<script type="text/javascript">
var before = function(){alert("before")};
var after = function(param){alert(param)};
var wrap = function(func, wrap_before, wrap_after){
wrap_before.call();
func.call();
wrap_after.call();
};
wrap(function(){alert("in the middle");},before,function(){after("after")});
</script>
You could do something like:
var wrap = function(func, pre, post)
{
return function()
{
var callee = arguments.callee;
var args = arguments;
pre();
func.apply(callee, args);
post();
};
};
This would allow you to do:
var someFunc = function(arg1, arg2)
{
console.log(arg1);
console.log(arg2);
};
someFunc = wrap(
someFunc,
function() { console.log("pre"); },
function() { console.log("post"); });
someFunc("Hello", 27);
Which gives me an output in Firebug of:
pre
Hello
27
post
The important part when wrapping this way, is passing your arguments from the new function back to the original function.
Maybe I'm wrong, but I think you can directly create an anonym function and assign it to myFunc:
myFunc = function(){
BeforeFunction();
myFunc();
AfterFunction();
}
In this way you can control the arguments of every function.

Javascript callback function and parameters [duplicate]

This question already has answers here:
Pass an extra argument to a callback function
(5 answers)
Closed 6 years ago.
I want to something similar to this:
function AjaxService()
{
this.Remove = function (id, call_back)
{
myWebService.Remove(id, CallBack)
}
function CallBack(res) {
call_back(res);
}
}
so my calling program will be like this:
var xx = new AjaxService();
xx.Remove(1,success);
function success(res)
{
}
Also if I want to add more parameters to success function how will I achieve it.
Say if I have success function like this:
var xx = new AjaxService();
//how to call back success function with these parameters
//xx.Remove(1,success(22,33));
function success(res,val1, val2)
{
}
Help will be appreciated.
Use a closure and a function factory:
function generateSuccess (var1,var2) {
return function (res) {
// use res, var1 and var2 in here
}
}
xx.Remove(1,generateSuccess(val1,val2));
What you're passing here is not the generateSuccess function but the anonymous function returned by generateSuccess that looks like the callback expected by Remove. val1 and val2 are passed into generateSuccess and captured by a closure in the returned anonymous function.
To be more clear, this is what's happening:
function generateSuccess (var1,var2) {
return function (res) {
// use res, var1 and var2 in here
}
}
var success = generateSuccess(val1,val2);
xx.Remove(1,success);
Or if you prefer to do it inline:
xx.Remove(1,(function(var1,var2) {
return function (res) {
// this is your success function
}
})(val1,val2));
not as readable but saves you from naming the factory function. If you're not doing this in a loop then Xinus's solution would also be fine and simpler than my inline version. But be aware that in a loop you need the double closure mechanism to disconnect the variable passed into the callback function from the variable in the current scope.
You can pass it as anonymous function pointer
xx.Remove(1,function(){
//function call will go here
success(res,val1, val2);
});
one way to do this:
function AjaxService {
var args_to_cb = [];
this.Remove = function (id, call_back, args_to_callback_as_array) {
if( args_to_callback_as_array!=undefined )
args_to_cb = args_to_callback_as_array;
else
args_to_cb = [];
myWebService.Remove(id, CallBack)
}
function CallBack(res) {
setTimeout( function(){ call_back(res, args_to_cb); }, 0 );
}
}
So you can use it like this:
var service = new AjaxService();
service.Remove(1,success, [22,33]);
function success(res,val1, val2)
{
alert("result = "+res);
alert("values are "+val1+" and "+val2);
}
I usually have the callback execute using a setTimeout. This way, your callback will execute when it gets the time to do so. Your code will continue to execute meanwhile, e.g:
var service = new AjaxService();
service.remove(1, function(){ alert('done'); }); // alert#1
alert('called service.remove'); // alert#2
Your callback will execute after alert#2.
Of course, in case of your application, it will happen so automatically since the ajax callback itself is asynchronous. So in your application, you had better not do this.
Cheers!
jrh

Categories