What is the difference between the following two functions as far as promises are concerned?
var service = {
someFunction: function() {
return $http.get('url').then(function (response) {
return response;
});
},
someFunction2: function() {
return $http.get('url');
}
};
The second returns a promise so that the calling function (not shown in your code) can do something with it.
The first one does exactly the same thing, but it also uses the promise to define a function that will run then the response comes back. That function does (effectively) nothing, since when the HTTP response arrives, it returns to the calling function (then) which doesn't care about the response.
There is no difference - the identity function in the first example is not needed but doesn't cause any observable difference to outside.
Related
I have a piece of code that submits a GET request to another part of my website in a function.
function getStatus(ID) {
$.get('/api/'+ID+'/info', function(statusCallback) {
return statusCallback;
});
}
console.log(getStatus(ID));
What I would expect this code to return and then log would be the information that I need.
What I actually get in console log is
undefined
What can I do to get the actual result?
You're doing async operation. In your case using callback. If you want to print statusCallback, you have to console.log it like Christos mentioned.
Also
console.log(getStatusId())
will return undefined because it's default value returned from the function that has no explicit return. Your function has no return statement, it's only calling some async method.
Btw, try promises instead of callbacks if you can ;)
With ES7 you can use async await
async function getStatus(ID) {
$.get('/api/'+ID+'/info', function(statusCallback) {
// This is executed in the future, but await knows how to deal with it
return statusCallback;
});
}
console.log(await getStatus(ID));
They are a good practice to get started with, because the code gets a lot easier to read.
You need to change your functions as below:
function getStatus(ID) {
$.get('/api/'+ID+'/info', function(statusCallback) {
console.log(statusCallback);
});
}
The function you pass as the second parameter of your get call is called success callback. It will be called, once the get request that is issued on each call of the getStatus function completes successfully. Only then you have access to what the server returned and you can fairly easy access it as above.
Update
If you want to return the data that server sends you have to declare first a variable
function getDataById(ID){
function callback(data){
return data;
}
$.get('/api/'+ID+'/info', function(data) {
return callback(data);
});
}
Just wanting to populate myObj with data from within the get request. According to my console.log(myObj) everythings seems to be their but can't access. I'm sure their is a simple answer to this. Thanks.
function getInfo() {
var myObj = {};
$.get("http://ipinfo.io", function(response) {
myObj.city = response.city;
myObj.region = response.region;
}, "jsonp");
console.log(myObj.city); //undefined
return myObj;
}
var myStuff = getInfo(); //Object {} just as I expected obviously.
console.log(myStuff) // Object shows all my key value pairs but -->
console.log(myStuff.city); // undefined why?
It is more suitable to use promises. You can try something like this:
function getInfo() {
var dfd = $.Deferred();
$.get("http://ipinfo.io", function(response) {
myObj.city = response.city;
myObj.region = response.region;
dfd.resolve(myObj);
}, "jsonp");
// Return the Promise so caller can't change the Deferred
return dfd.promise();
}
getInfo().done(function(myStuff) {
console.log(myStuff);
console.log(myStuff.city);
});
With promises the code is one idea elegant.
$.get is an asynchronous operation which means that it returns immediately (without running the code in your callback function first). The callback will only be executed at some point in the future when the GET request is finished. In the meantime the code following the $.get call will be executed.
What you're seeing is a result of a specific execution order in which the GET request happened not to finish before the following code was executed. In other words you have a race condition.
In theory , if the GET request finished almost instantly, it would be possible for the callback to execute before your log statements and therefore not print undefined. This is highly unlikely, however, as the time it takes to execute log instructions are much(!) less than what it takes to execute the GET request.
To fix the issue you need to ensure that you're log statements are always run only after the GET request is finished. The idiomatic way to do this in JS is with callbacks - e.g. pass a function which contains the code to execute and call it from inside the response handler:
function getInfo(callback) {
var myObj = {};
$.get("http://ipinfo.io", function(response) {
myObj.city = response.city;
myObj.region = response.region;
callback(myObj);
}, "jsonp");
console.log(myObj.city); //undefined
}
getInfo(function(myStuff) {
console.log(myStuff)
console.log(myStuff.city);
});
It return undefine because the request didn't response yet then the function returned the value. Your request was asynchronous meaning your function wont wait for your get request. it would return what ever myObj is.
You can try something like
function getInfo(callback){
$.get(..., function(res){
var myObj = …;
callback(myObj);
}
}
when ever your request is finished, it would call the callback function and pass in the myObj. so you need to pass in a anonymous function that expect 1 argument to pass in the myObj from the response
getInfo(function(data){
//data should be myObj
});
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.
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');
I created this plunker.
The gist is this code:
var myObj = function (){
return {
getThis: function (callback) {
return callback("this");
}
}
}
var myDidSome = function () {
return {
DoSome: function (text) {
return text + " is cool";
}
};
};
var subObj = function (objG, didSome) {
var txt = objG.getThis(function (text) {
return didSome.DoSome(text);
});
console.log(txt);
}
new subObj(new myObj, new myDidSome);
So to deconstruct this code, view myObj and myDidSome as services (angular ones). I'm most interested in the myObj.getThis, this is supposed to mimick some $http response. So when its finished call my callback function passing in the parameters obtained from $http.
The thing I keep thinking about though is $http is async. Will my txt property in the subOj (think of this as a controller) class be populated correctly when the data gets back, I assume it will be undefined till then?
In my scenario this is completely synchronous, which obviously works.
So, I would like to know whether async will be an issue here? And whether doing this practice of propagating the return of an object is a good one, or doesn't matter, or is bad and I shouldn't do it?
You cannot use return like that with an asynchronous method. You have to do everything within the callback. In your example, the result of didSome.DoSome(text) will never be assigned to txt, not even after the callback has run.
For a simple case like this where you're logging the response, doing it in the callback is an acceptable approach. If you need to do something more complex, however, you should look into promises via $q. These provide a much nicer way to rewrite a long chain of callbacks into a more linear flow.