check if callback is registered with angular's deferred object [duplicate] - angularjs

I want to return a $q instance so that if clients don't call 'then' with a reject handler, then a default one runs.
E.g. assume the default is to alert(1)
Then mypromise.then(function(result){...}) will alert 1 but mypromise.then(null, function(reason){alert(2)}) will alert 2

Let's assume there was a way to do that.
So we have a magical machine that can always find out if .then is called with a function second argument for a specific promise or not.
So what?
So you can detect if someone did:
myPromise.then(..., function(){ F(); });
At any point from anywhere at any time. And have G() as a default action.
And...?
You could take a whole program containing lots of code P (1) and convert that code to:
var myPromise = $q.reject();
P; // inline the program's code
myPromise.then(null, function(){}); // attach a handler
Great, so I can do that, so?
Well, now our magical machine can take an arbitrary program P and detect if myPromise had a rejection handler added to it. Now this happens if and only if P does not contain an infinite loop (i.e. it halts). Thus, our method of detecting if a catch handler is ever added is reduced to the halting problem. Which is impossible. (2)
So generally - it is impossible to detect if a .catch handler is ever attached to a promise.
Stop with the mumbu-jumbo, I want a solution!
Good response! Like many problems this one is theoretically impossible but in practice easy enough to solve for practical cases. The key here is a heuristic:
If an error handler is not attached within a microtask (digest in Angular) - no error handlers are ever attached and we can fire the default handler instead.
That is roughly: You never .then(null, function(){}) asynchronously. Promises are resolved asynchronously but the handlers are usually attached synchronously so this works nicely.
// keeping as library agnostic as possible.
var p = myPromiseSource(); // get a promise from source
var then = p.then; // in 1.3+ you can get the constructor and use prototype instead
var t = setTimeout(function(){ // in angular use $timeout, not a microtask but ok
defaultActionCall(p);// perform the default action!
});
// .catch delegates to `.then` in virtually every library I read so just `then`
p.then = function then(onFulfilled, onRejected){
// delegate, I omitted progression since no one should use it ever anyway.
if(typeof onRejected === "function"){ // remove default action
clearTimeout(t); // `timeout.cancel(t)` in Angular
}
return then.call(this, onFulfilled, onRejected);
};
Is that all?
Well, I just want to add that cases where this extreme approach is needed are rare. When discussing adding rejection tracking to io - several people suggested that if a promise is rejected without a catch then the whole app should likely terminate. So take extra care :)
(1) assume P does not contain a variable myPromise, if it does rename myPromise to something P does not contain.
(2) Of course - one can say that it is enough to read the code of P and not run it in order to detect myPromise gets a rejection handler. Formally we say that we change every return in P and other forms of termination to a return myPromise.then(null, function(){}) instead of simply putting it in the end. this way the "conditionality" is captured.

Related

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.

Test multiple listeners with Jasmine

I need to test every listener in a controller with Jasmine 2.0, really this question is just to reinforce my logic, and perhaps there is a more elegant way to approach testing listeners, or maybe I am being too thorough!
This might be a better question for codereview but I'll leave it here. How to properly test multiple keypress/event listeners in a controller effectively?
it("should trigger the correct actions from key events", function () {
var listenerSpy = jasmine.createSpy('listenerSpy');
angular.forEach(scope.$$listeners, function (fn, eventName) {
listenerSpy(eventName, fn);
expect(listenerSpy).toHaveBeenCalledWith(eventName, fn);
});
});
What you have above is not really testing anything other than JavaScript itself. You are calling a function and then expecting that you just called that function.
A code coverage report would show that the listener function has not executed at all.
Without seeing the code you are testing, I am unable to properly advise on how to structure your test.
There are two possible intentions:
1) Do you want to test that the scope is listening to a set of known elements?
2) Do you want to test the outcome of listener executions?
Usually, it would be best to take path number two, because with it, you also get number one.
Are all of your listeners performing the same action?
If they are, it may make sense to loop through a list of known elements and change them to verify the proper listener execution output.
If the listeners perform differently, each execution's output should be evaluated.

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.

Watch doesn't fire when I write the same value

I have a watch
$scope.$watchCollection('[deal.curr1, deal.curr2]', function (newValues) {
...
}
I then change the value in code:
$scope.deal.curr1 ="USD";
But in a case curr1 was already "USD",
watch wont be called.
But I would like it to be called anyway.
Setting null before doesn't help.
$scope.deal.curr1 =null;
$scope.deal.curr1 ="USD";
setting null and then other value won't work because the evals are done after each cycle if you waned to do that you would have to set it to null and then use $timeout.
$scope.var=null
$timeout(function(){
$scope.val="value";
})
To assign the new value on the next cycle, but this would run the validation twice.
now the question is. Why would you want to run the the watch function if the value remains the same? this a protection to avoid making unnecessary calls and improve performance. if the value doesn't change there shouldn't be a valid reason for the watcher to react.
If you still must i'll advice to observe other parameter if possible that would give some indication that some action took place.
could you elaborate more in your use case?
The listener only gets fired when the watchExpression is different in the last two calls. Since you just want to fire the listener, you can just call it manually. Scope
EDIT: since the listener is always called after the $http call, just call the listener in the success handler of the $http promise and ditch $watchCollection
$http(...).then(function(payload){
//things you do with the resonse. like setting $scope.deal.curr1
$scope.listener(newVal);
}, function(reason){...})

jQuery Promises and Backbone

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.

Resources