Object method with ES6 / Bluebird promises - javascript

I am using node v0.11.14-nightly-20140819-pre on Windows with harmony flag.
I have JavaScript object with two methods defined in its prototype:
function User (args) {
this.service= new Service(args);
}
User.prototype.method2 = function (response) {
console.log(this); // <= UNDEFINED!!!!
};
User.prototype.method1 = function () {
.............
this.service.serviceMethod(args)
.then(this.method2)
.catch(onRejected);
};
function onRejected(val) {
console.log(val);
}
serviceMethod of Service object returns a promise.
When I use User object like below:
let user = new User(args);
user.method1();
this in method2 of object User ends up undefined when called by then once promise is fulfilled.
I tried using both ES6 and Bluebird promise implementation.
Why this ends up being undefined in this case?

Why this ends up being undefined in this case?
Because you're passing a function, not a method-bound-to-an-instance. This problem is not even promise-specific, see How to access the correct `this` context inside a callback? for the generic solution:
….then(this.method2.bind(this))… // ES5 .bind() Function method
….then((r) => this.method2(r))… // ES6 arrow function
However, Bluebird does offer an other way to call the function as a method:
this.service.serviceMethod(args)
.bind(this)
.then(this.method2)
.catch(onRejected);

I should add that this is a generic Javascript issue and can also be solved using plain javascript features. For example, you could also do this:
User.prototype.method1 = function () {
.............
this.service.serviceMethod(args)
.then(this.method2.bind(this))
.catch(onRejected);
};
This uses Function.prototype.bind() which is built into Javascript and present on every function. This creates a function stub (which is what is passed to .then() and that stub will automatically reattach the desired this value before calling method2().

Related

How can I use ".call()" method in a javascript function and why does it have a "null" value?

So basically why do I have to use this kind of method in these kind of situations in particular?
function Example(callback) {
// What's the purpose of both 'call()' and 'null'?
callback.call(null, "Hello")
}
Exemple(function(callback) {
alert();
})
I've figured it out this syntax in a open project code but I haven't found out why it works yet.
You don't need to use call() in this situation. call() is used when you need to set the value of this in a function. If you aren't doing that, just call the function like you would any other function. For example:
function Example(callback) {
// call the function passed in
callback("Hello")
// this also works, but there's no benefit here
// because the callback doesn't care about `this`
// callback.call(null, "Hello")
}
// pass a callback function to Example
Example(function(text) {
console.log(text);
})
You would use call in situations where the callback function needs a value for this. For example:
function Example(callback) {
let person = {name: "Mark"}
// set this in the callback to the person object
// and the execute it with the argument "Howdy"
callback.call(person, "Howdy")
}
Example(function(greeting) {
// the callback function depends on having this.name defined
console.log(greeting, this.name);
})
This looks pointless at the first glance, however, there might be valid reasons for this syntax, for example:
the callback relies explicitly on this being null and not some global default
the callback is not necessarily a function, it can be a "function-alike" object that provides a .call method
callback.call(... is used consistently through the application, no matter if this is needed or not

Why is 'this' undefined inside class method when using promises? [duplicate]

This question already has answers here:
setTimeout and "this" in JavaScript
(5 answers)
Closed 7 years ago.
I have a javascript class, and each method returns a Q promise. I want to know why this is undefined in method2 and method3. Is there a more correct way to write this code?
function MyClass(opts){
this.options = opts;
return this.method1()
.then(this.method2)
.then(this.method3);
}
MyClass.prototype.method1 = function(){
// ...q stuff...
console.log(this.options); // logs "opts" object
return deferred.promise;
};
MyClass.prototype.method2 = function(method1resolve){
// ...q stuff...
console.log(this); // logs undefined
return deferred.promise;
};
MyClass.prototype.method3 = function(method2resolve){
// ...q stuff...
console.log(this); // logs undefined
return deferred.promise;
};
I can fix this by using bind:
function MyClass(opts){
this.options = opts;
return this.method1()
.then(this.method2.bind(this))
.then(this.method3.bind(this));
}
But not entirely sure why bind is necessary; is .then() killing this off?
this is always the object the method is called on. However, when passing the method to then(), you are not calling it! The method will be stored somewhere and called from there later. If you want to preserve this, you will have to do it like this:
.then(() => this.method2())
or if you have to do it the pre-ES6 way, you need to preserve this before:
var that = this;
// ...
.then(function() { that.method2() })
Promise handlers are called in the context of the global object (window) by default. When in strict mode (use strict;), the context is undefined. This is what's happening to method2 and method3.
;(function(){
'use strict'
Promise.resolve('foo').then(function(){console.log(this)}); // undefined
}());
;(function(){
Promise.resolve('foo').then(function(){console.log(this)}); // window
}());
For method1, you're calling method1 as this.method1(). This way of calling it calls it in the context of the this object which is your instance. That's why the context inside method1 is the instance.
Basically, you're passing it a function reference with no context reference. The this context is determined in a few ways:
Implicitly. Calling a global function or a function without a binding assumes a global context.*
By direct reference. If you call myObj.f() then myObj is going to be the this context.**
Manual binding. This is your class of functions such as .bind and .apply. These you explicitly state what the this context is. These always take precedence over the previous two.
In your example, you're passing a function reference, so at it's invocation it's implied to be a global function or one without context. Using .bind resolves this by creating a new function where this is explicitly set.
*This is only true in non-strict mode. In strict mode, this is set to undefined.
**Assuming the function you're using hasn't been manually bound.
One way functions get their context (this) is from the object on which they are invoked (which is why method1 has the right context - it's invoked on this). You are passing a reference to the function itself to then. You can imagine that the implementation of then looks something like this:
function then( callback ) {
// assume 'value' is the recently-fulfilled promise value
callback(value);
}
In that example callback is a reference to your function. It doesn't have any context. As you've already noted you can get around that by binding the function to a context before you pass it to then.

Resolve value in javascript

I have the following code:
function doSomething() {
//xhr here
setTimeout(function() {
var value = 42;
}, 10);
return {
then: function(callback) {
callback(value);
}
};
}
doSomething().then(function(result) {
log("got a result", result);
});
And can't figure out how to access the value.
I need this to be promise-based solution in order to use in multiple places
JSFidle link
Update:
We are not using any libraries in that projects
There are a couple of problems there:
value is local to the function you're passing into setTimeout, because that's where you've declared it. You could fix this issue by declaring it in doSomething instead.
The bigger issue is that what you have there isn't a promise, it's just a function that returns an object when you call it that has a then method. Here's the order in which things happen:
You call doSomething
It sets a timer to set a value.
It creates an object with a then function.
It returns the object.
You call the then function immediately.
then tries to access value (which it can't because of the declaration issue, but would be a problem anyway).
Some time later, value is set by the callback when the timer fires.
To be a promise, the then function on object you return would have to store a reference to the callback passed into it, and call the callback later, when value has been set (e.g., the promise has been fulfilled).
Rather than implementing your own promises library, I'd suggest using one of the several that have already been written and debugged.
I'm just pointing out, that in order to "fix" your issue you need to return the then this way:
function doSomething() {
//xhr here
return {
then: function(callback) {
setTimeout(function() {
callback(42); // callback from within the async action
}, 10);
}
};
}
doSomething().then(function(result) {
log("got a result", result);
});
Please read TJ's answer and consider using a promise library - also consider reading this post that explains how promise resolution looks like.
Namely: Your then needs to in turn return a promise when called (rather than just set a timeout) for chaining, and the callback should be assimilated before waiting for it.

Javascript async function composition

I have several async functions with varying numbers of parameters, in each the last param is a callback. I wish to call these in order. For instance.
function getData(url, callback){
}
function parseData(data, callback){
}
By using this:
Function.prototype.then = function(f){
var ff = this;
return function(){ ff.apply(null, [].slice.call(arguments).concat(f)) }
}
it is possible to call these functions like this, and have the output print to console.log.
getData.then(parseData.then(console.log.bind(console)))('/mydata.json');
I've been trying to use this syntax instead, and cannot get the Then function correct. Any ideas?
getData.then(parseData).then(console.log.bind(console))('/mydata.json');
Implementing a function or library that allows you to chain methods like above is a non-trivial task and requires substantial effort. The main problem with the example above is the constant context changing - it is very difficult to manage the state of the call chain without memory leaks (i.e. saving a reference to all chained functions into a module-level variable -> GC will never free the functions from memory).
If you are interested in this kind of programming strategy I highly encourage you to use an existing, established and well-tested library, like Promise or q. I personally recommend the former as it attempts to behave as close as possible to ECMAScript 6's Promise specification.
For educational purposes, I recommend you take a look at how the Promise library works internally - I am quite sure you will learn a lot by inspecting its source code and playing around with it.
Robert Rossmann is right. But I'm willing to answer purely for academic purposes.
Let's simplify your code to:
Function.prototype.then = function (callback){
var inner = this;
return function (arg) { return inner(arg, callback); }
}
and:
function getData(url, callback) {
...
}
Let's analyze the types of each function:
getData is (string, function(argument, ...)) → null.
function(argument, function).then is (function(argument, ...)) → function(argument).
That's the core of the problem. When you do:
getData.then(function (argument) {}) it actually returns a function with the type function(argument). That's why .then can't be called onto it, because .then expects to be called onto a function(argument, function) type.
What you want to do, is wrap the callback function. (In the case of getData.then(parseData).then(f), you want to wrap parseData with f, not the result of getData.then(parseData).
Here's my solution:
Function.prototype.setCallback = function (c) { this.callback = c; }
Function.prototype.getCallback = function () { return this.callback; }
Function.prototype.then = function (f) {
var ff = this;
var outer = function () {
var callback = outer.getCallback();
return ff.apply(null, [].slice.call(arguments).concat(callback));
};
if (this.getCallback() === undefined) {
outer.setCallback(f);
} else {
outer.setCallback(ff.getCallback().then(f));
}
return outer;
}
This looks like an excellent use for the Promise object. Promises improve reusability of callback functions by providing a common interface to asynchronous computation. Instead of having each function accept a callback parameter, Promises allow you to encapsulate the asynchronous part of your function in a Promise object. Then you can use the Promise methods (Promise.all, Promise.prototype.then) to chain your asynchronous operations together. Here's how your example translates:
// Instead of accepting both a url and a callback, you accept just a url. Rather than
// thinking about a Promise as a function that returns data, you can think of it as
// data that hasn't loaded or doesn't exist yet (i.e., promised data).
function getData(url) {
return new Promise(function (resolve, reject) {
// Use resolve as the callback parameter.
});
}
function parseData(data) {
// Does parseData really need to be asynchronous? If not leave out the
// Promise and write this function synchronously.
return new Promise(function (resolve, reject) {
});
}
getData("someurl").then(parseData).then(function (data) {
console.log(data);
});
// or with a synchronous parseData
getData("someurl").then(function (data) {
console.log(parseData(data));
});
Also, I should note that Promises currently don't have excellent browser support. Luckily you're covered since there are plenty of polyfills such as this one that provide much of the same functionality as native Promises.
Edit:
Alternatively, instead of changing the Function.prototype, how about implementing a chain method that takes as input a list of asynchronous functions and a seed value and pipes that seed value through each async function:
function chainAsync(seed, functions, callback) {
if (functions.length === 0) callback(seed);
functions[0](seed, function (value) {
chainAsync(value, functions.slice(1), callback);
});
}
chainAsync("someurl", [getData, parseData], function (data) {
console.log(data);
});
Edit Again:
The solutions presented above are far from robust, if you want a more extensive solution check out something like https://github.com/caolan/async.
I had some thoughts about that problem and created the following code which kinda meets your requirements. Still - I know that this concept is far away from perfect. The reasons are commented in the code and below.
Function.prototype._thenify = {
queue:[],
then:function(nextOne){
// Push the item to the queue
this._thenify.queue.push(nextOne);
return this;
},
handOver:function(){
// hand over the data to the next function, calling it in the same context (so we dont loose the queue)
this._thenify.queue.shift().apply(this, arguments);
return this;
}
}
Function.prototype.then = function(){ return this._thenify.then.apply(this, arguments) };
Function.prototype.handOver = function(){ return this._thenify.handOver.apply(this, arguments) };
function getData(json){
// simulate asyncronous call
setTimeout(function(){ getData.handOver(json, 'params from getData'); }, 10);
// we cant call this.handOver() because a new context is created for every function-call
// That means you have to do it like this or bind the context of from getData to the function itself
// which means every time the function is called you have the same context
}
function parseData(){
// simulate asyncronous call
setTimeout(function(){ parseData.handOver('params from parseData'); }, 10);
// Here we can use this.handOver cause parseData is called in the context of getData
// for clarity-reasons I let it like that
}
getData
.then(function(){ console.log(arguments); this.handOver(); }) // see how we can use this here
.then(parseData)
.then(console.log)('/mydata.json'); // Here we actually starting the chain with the call of the function
// To call the chain in the getData-context (so you can always do this.handOver()) do it like that:
// getData
// .then(function(){ console.log(arguments); this.handOver(); })
// .then(parseData)
// .then(console.log).bind(getData)('/mydata.json');
Problems and Facts:
the complete chain is executed in the context of the first function
you have to use the function itself to call handOver at least with the first Element of the chain
if you create a new chain using the function you already used, it will conflict when it runs to the same time
it is possible to use a function twice in the chain (e.g. getData)
because of the shared conext you can set a property in one function and read it in one of the following functions
At least for the first Problem you could solve it with not calling the next function in the chain in the same context and instead give the queue as parameter to the next function. I will try this approach later. This maybe would solve the conflicts mentioned at point 3, too.
For the other problem you could use the sample Code in the comments
PS: When you run the snipped make sure your console is open to see the output
PPS: Every comment on this approach is welcome!
The problem is that then returns a wrapper for the current function and successive chained calls will wrap it again, instead of wrapping the previous callback. One way to achieve that is to use closures and overwrite then on each call:
Function.prototype.then = function(f){
var ff = this;
function wrapCallback(previousCallback, callback) {
var wrapper = function(){
previousCallback.apply(null, [].slice.call(arguments).concat(callback));
};
ff.then = wrapper.then = function(f) {
callback = wrapCallback(callback, f); //a new chained call, so wrap the callback
return ff;
}
return wrapper;
}
return ff = wrapCallback(this, f); //"replace" the original function with the wrapper and return that
}
/*
* Example
*/
function getData(json, callback){
setTimeout( function() { callback(json) }, 100);
}
function parseData(data, callback){
callback(data, 'Hello');
}
function doSomething(data, text, callback) {
callback(text);
}
function printData(data) {
console.log(data); //should print 'Hello'
}
getData
.then(parseData)
.then(doSomething)
.then(printData)('/mydata.json');

When I use a method as a callback, it seems to lose access to `this`. Why?

I'm using express in Node to create a simple web app. The code looks like this:
var get_stuff = function (callback) {
another.getter(args, function (err, data) {
if (err) throw err;
data = do_stuff_to(data);
callback(data);
});
};
app.get('/endpoint', function (req, res) {
get_stuff(res.send);
});
When I run this, though, I get this error: TypeError: Cannot read property 'method' of undefined at res.send. The express code that's breaking starts like this:
res.send = function (body) {
var req = this.req;
var head = 'HEAD' == req.method;
It seems to me that the way I've constructed the callbacks is losing this in the send method. But I'm not sure how to fix it. Any tips? Thanks!
Call .bind:
get_stuff(res.send.bind(res));
and have a look at the MDN documentation about this to get an idea how it works. The value of this is determined by how the function is called. Calling it "normally" (what probably happens with the callback), like
func();
will set this to the global object. Only if a function is called as an object method (or if this is explicitly set with .bind, .apply or .call are used), this refers to the object:
obj.fun(); // `this` refers to `obj` inside the function
.bind allows you to specify the this value without calling the function. It simply returns a new function, similar to
function bind(func, this_obj) {
return function() {
func.apply(this_obj, arguments);
};
}
In JavaScript, the value of this is generally determined by the call site, and unlike in Python, accessing a method via the . operator does not bind its left-hand side to this when the method is later called.
To perform the binding, you can call .bind like in the older answer, or you may perform the binding by hand, wrapping the method call in another callback:
get_stuff(function () {
return res.send.apply(res, arguments);
});
As of ECMAScript 2018, it’s also possible to use fat-arrow function syntax and rest parameters to make the above much more compact:
get_stuff((...args) => res.send(...args));

Categories

Resources