jQuery Promises and Backbone - backbone.js

I found this snippet of code that does what I want it to:
var promise = this.model.save();
$.when(promise).then(function() {
console.log(promise.responseText);
});
I want to get back the responseText from my Backbone call to this.model.save(). This code was documented here. But it's not logging anything, even if I pull a raw text string in the console.log() call.
Could someone please explain in layman's terms what a jQuery promise is? I've read about them, but I don't think I quite got what they were. That might help me understand why this code isn't working for me. If I console.log(promise) in between the first and second lines of code, then I get the responseText. So something is happening in either the $.when or the then that is causing this to go wrong.
EDIT:
After reading the article, I discovered I could do this:
var promise = this.model.save();
$.when(promise).then(null, function(obj) {
console.log(obj.responseText);
});
But I don't understand what the null represents. then seems to take two parameters, a success function and a failure function. But wouldn't the success function be first? I get a 200 response from the server.

So first off, I'm pretty sure you don't need the when part; from the jQuery docs:
The jqXHR objects returned by $.ajax() as of jQuery 1.5 implement the
Promise interface, giving them all the properties, methods, and
behavior of a Promise (see Deferred object for more information).
Since Promise has a then method already, you can just do:
this.model.save().then(null, function(obj) {
console.log(obj.responseText);
});
(The fact that the above code almost reads like an English sentence is a major advantage of using Deferreds, for me at least.)
As for your null argument, the docs are again pretty clear. There are three signatures for then (and that's just to cover the different jQuery versions; any given version has less):
deferred.then( doneFilter [, failFilter ] [, progressFilter ] )
deferred.then( doneCallbacks, failCallbacks )
deferred.then( doneCallbacks, failCallbacks [, progressCallbacks ] )
As you can see, all three take the "done" function first, and the failure function second. This does seem to imply that you're getting a failure, which is confusing. One way to avoid the problem is to not use then at all. Instead, try the following:
this.model.save().always(function(obj) {
console.log(obj.responseText);
});
That will make your function get called no matter what happens. However, you probably should figure out what's going on, so you might want to instead add a success and failure callback to do some debugging:
this.model.save().done(function() {
// Success case
}).fail(function() {
// Failure case
});

Because this.model.save returns a promise, you can do the following instead:
this.model.save()
.done(function(response) {
console.log("Success!");
})
.fail(function(response) {
console.log("Error!");
});
(That's easier than the whole $.when bit.)
My guess is that although your response is returning a 200 code, it is still "failing" because the response data type doesn't match up with what you're expecting (what's set in the dataType attribute in the $.ajax call).

I am a big fan of using promise, and I think the promise means very similar things in different packages.
To answer your question, which previous answers didn't, the "then" function is a function of a promise, the "when" function is a fail-save, incase the object is not a promise, a "when(obj)" will make sure it is a promise, so that you can use the elegant xxx.then(success(){},error(){}).
btw, the "deferred" that machineghost said is the package that let you use promise.
For starting to know promise and how to use it. check out this tutorial. It explains every thing very clearly, it is the article that made me so into promises.
http://strongloop.com/strongblog/promises-in-node-js-with-q-an-alternative-to-callbacks/
Now, as machineghost said, it seems your sync call is getting an error, according to a REST API documentation,https://parse.com/docs/rest# (don't know if it is the same as backbone), the server will response a JSON object for a "create" request in this format:
{"createdAt": "2011-08-20T02:06:57.931Z","objectId": "Ed1nuqPvcm"}
My guess is, maybe your server did not respond the request with the correct JSON, so the save() think the operation failed because there was no "createAt" field, event thought your sever did created the item.

Related

Remove $promise and $resolved from json object not working

I'm getting data from the server using $resource like this
service
.factory('rulesService', ['$resource', function ($resource) {
var systems = $resource('url');
return systems;
}]);
controller
$scope.rules= rulesService.query();
console.log($scope.rules);
The output I get is
0: Resource
1: Resource
$promise: Promise
$resolved: true
length: 2
I tried to strip $promise & $resolved using
1) angular.toJson($scope.rules)
2)JSON.stringify($scope.rules, null, 2)
Both these are returning []
Can someone help me on this
After reading your comments above, I think your problem is you use $resource wrong.
$resource will return a empty object or array, and then make the http call in the background and populate the array/object once it is complete.
This means that, at the time you console.log the object, it is actually an empty array, but since the log in the browser is pretty smart, it will update the object in the log as well, once the $resource call is done.
this is why console.log(rules[0]) is undefined, while console.log(rules) says the element exists. It didn't at the time of the log.
If you need to do further processing you have to do something like:
var rules = rulesService.query(function () {
console.log(rules[0])
})
you can also use promises instead of a callback, but either way you need to ensure the data is fully loaded
You should be able to simply iterate over the array (and ignore the extra properties). If you want a clean array you could always use a map or similar.
$scope.new_rules = $scope.rules.map(function (rule){
return rule;
})
Your problem is related to asynchronous execution and race conditions.
You're trying to refer to rules[0] before data actually arrives from the $resource.query() call.
Examples:
var rules = rulesService.query();
console.log(rules[0]); //will print nothing. the GET request hasn't been resolved yet.
rules.$promise.then(function (response) {
console.log(rules[0]); //This WILL work assuming you actually get data from the backend.
console.log(response[0]); //This will also work with the same data.
});

Angular and Meteor flicker on page load

I had an issue with and angular-meteor project flickering every time a state using the campaigns subscription, would load. By flickering, I mean the data was there, then it would go away and come back a half second later.
I added this to the resolve property of the state (using ui-router):
campaigns: ($q) => {
var deferred = $q.defer();
Meteor.subscribe('campaigns', {
onReady: deferred.resolve,
onStop: deferred.reject
});
return deferred.promise;
}
The flickering stopped, but I don't really understand this code. Can someone who understand angular break this resolve/defer situation down?
Just not sure why it worked. thanks.
$q is angular's implementation of promises.
in a very itty bitty nutshell, a promise has two callbacks that resolve when data is returned; a resolve function if the call succeeds and a reject if the call fails. whatever data it gets will be passed into these functions (essentially doing deferred.resolve(data) or deferred.reject(error)) . $q.defer() allows us to assign the resolution/rejections later.
meteor's subscribe function takes a few arguments. the string name of the collection, a function that returns an array of arguments to be passed to the collection, and an object/function. the object part of the final argument expects an "onReady" and "onStop" functions, and will execute those functions and pass along any data it gets. we pass in our callbacks here.
finally, we return the promise. resolve.campaigns will be a promise, which we can get the values from using the .then(successCallback, failureCallback) call. meteor handles this behind the scenes.

Dealing synchronously in Protractor tests

I'm trying to write what I think is a fairly simple test in protractor, but it would seem that the minute you try to do anything synchronously, Protractor makes life hard for you! Normally, dealing with locator functions (that return a promise) are not an issue, since any expect statement will automatically resolve any promise statement passed to it before testing the assertion. However, what I'm trying to do involves resolving these locator promises before the expect statement so that I can conditionally execute some test logic. Consider (pseudocode):
// Imagine I have a number of possible elements on the page
// and I wish to know which are on the page before continuing with a test.
forEach(elementImLookingFor){
if (elementImLookingFor.isPresent) {
// record the fact that the element is (or isnt) present
}
}
// Now do something for the elements that were not found
However, in my above example, the 'isPresent' call returns a promise, so can't actually be called in that way. Calling it as a promise (i.e. with a then) means that my forEach block exits before I've recorded if the element is present on the page or not.
I'm drawing a blank about how to go about this, has anyone encountered something similar?
I've used bluebird to do the following;
it('element should be present', function(done)
Promise.cast(elementImLookingFor.isPresent)
.then(function(present){
expect(present).toBeTruthy();
})
.nodeify(done);
});
If you have a few elements that you want to check the isPresent on you should be able to do the following;
it('check all elements are present', function(done){
var promises = [element1, element2].map(function(elm){
return elm.isPresent();
});
// wait until all promises resolve
Promise.all(promises)
.then(function(presentValues){
// check that all resolved values is true
expect(presentValues.every(function(present){
return present;
})).toBeTruthy();
})
.nodeify(done);
});
Hope this helps
So elementImLookingFor is a promise returned by element.all, I presume? Or, as stated in the Protractor documentation, an ElementArrayFinder. You can call the method .each() on it and pass it a function that expects things.

Pattern for returning data from Angular AJAX calls

Take a look at this method returned in a factory:
fetchEmployeeList: function() {
var q = $q.defer();
$http.get('/Employee/')
.success(q.resolve)
.error(ajaxErrorHandler.handleError);
return q.promise;
}
The author of this code says that this is the model we should use for returning data from HTTP endpoints. Basically, any time we need data from a service, we should use this pattern. The author is under the impression that this is preferred over returning $http.get()'s return value instead.
I don't understand the point of this code, though. I thought $http.get() does return a promise.
Can someone explain what this example snippet is doing or what they think the author might be trying to do?
That's the deferred anti-pattern, which practically warps a promise when not needed.
in your case returning a q.promise seems like an abuse since the HTTP object can return a promise itself.
I'd refactor the code to the following:
fetchEmployeeList: function() {
return $http.get('/Employee/');
}
you can take a look to this blog post for more reference as well,
don't be afraid to open a discussion with whoever is suggesting that approach.

Backbone model.save() not calling either error or success callbacks

I'm trying to update a record in DB so I'm defining model with data and calling .save() method. The PUT request is triggered and the database entry is updated. The problem is neither success or error callbacks are called. What could be the cause?
sessionsModel.save({
error: function() {
alert('test');
},
success: function () {
alert('test');
}
});
Edit: Request returns JSON object
Just found similar problem where the issue was solved. You have to put something as first parameter (I put null since my model was already populated with data explicitly) and object with callbacks as second. So something like;
sessionsModel.save(null, {success:function() {} });
While searching on this, I first landed on this SO thread which did not work for me, but seemed to work for other, later on I bumped into this link, where some one had tried null instead of {} as the first parameter.
this.model.save(null, {
success: function (model, response) {
console.log("success");
},
error: function (model, response) {
console.log("error");
}
});
so, this worked for me. Hope this helps you too.
Your server must return a JSON object. If the response is not a JSON object, the callbacks will not fire.
Check this solution https://stackoverflow.com/a/22176044/1579718
I was suffering this issue - but was struggling because my server was responding with a valid JSON object (the model) and I was already using null in my save call.
As I (eventually) found, before the success callback is fired, the returned model is passed through the validate method. In my case I had a (obvious when you're looking in the right place) problem which caused the returned model to be deemed invalid and subsequently prevent the success callback.
Whilst I appreciate this doesn't help the OP, I post this in the hope it helps someone else having the same issue.

Resources