How to structure Protractor promises - javascript

In this example I'm testing whether an element is located at the bottom of the page. I'm using the promises in Protractor/Webdriver, but I think I'm using it wrong because I don't think it's supposed to look this messy.
describe('Element', function(){
it('should be placed at bottom', function(){
element(by.id('page').getSize().then(function(dimP){
element(by.id('element').getLocation().then(function(locE){
element(by.id('element').getSize().then(function(dimE){
expect(locE.y + dimE.height).toEqual(dimP.height);
})
});
});
})
});
How can I do this in a cleaner way? I'm using Protractor version 2.1.0.
I have tried to do like this:
expect(element(by.id('page').getSize().height).toEqual(1000);
But it says Expected undefined to equal 1000. So it seems like I can't use return values as explained here: https://github.com/angular/protractor/blob/master/docs/control-flow.md
(I am using a page object in my actual test, so the variables are not as ugly as in the example.)

Since getSize() and getLocation() both return promises and you need actual values, you have to resolve all of the promises, either explicitly with then(), or let expect() implicitly do this for you.
I'm actually understanding your question as "How to flatten a promise chain in Protractor?". This is there promise.all() would help to resolve multiple promises at once which would save you from writing multiple nested then() callbacks:
var elm = element(by.id('element')),
page = element(by.id('page'));
var promises = [elm.getSize(), elm.getLocation(), page.getSize()];
protractor.promise.all(promises).then(function (values) {
expect(values[0].height + values[1].y).toEqual(values[2].height);
});
See also:
Flattening Promise Chains
How do I reference a promise returned from a chained function?
How do I access previous promise results in a .then() chain?

Related

sequential promise chain with array of data, how to resolve the promise chain?

I am using kriskowal q implementation.
I have an array of data objects, each with an id.
I need to chain these sequentially into promises because I am abiding to rate limiting rules by setting it to 1 request per second.
However, I am having trouble resolving the promises and my function stalls. I.e. I see the ouput of addVideo, getInfo, retryIfNeeded and a delay of 1 second for the very first video, but I don't see any of that for any subsequent videos.
What I want to do is after the delay, to resolve that chain so that the next list of promises continues on the second Video ID.
How do I do this? What am I doing wrong? I've searched a lot on google but haven't found a solution so any suggestions is welcome
Edit added jsfiddle: http://jsfiddle.net/gpa7ym18/4
var promiseChain = data.items.reduce(function(promise, video) {
video.type = type;
return promise
.then(addVideo)
.then(getInfo)
.then(retryIfNeeded)
.then( function() {
return q.delay(1000)
.done(function() {
NEED TO RESOLVE HERE but there is NO Defered object
to set defer.resolve. How do I resolve this promise chain?
});
});
}, q.resolve(data.items[0]));
You don't need to resolve anything.
You should simply return the delayed promise, and that will become the value of the entire chain.
This is exactly how promise chaining works.

Does Angular have a predefined object that houses all current promises?

I have a controller where I can allow everything except one function ( let's call it thatOneFunction() ) to run asynchronously. The reason for that is thatOneFunction() relies on roughly 10 promises to have been resolved otherwise it does not have the data in the scope it needs to execute.
I have seen multiple examples online of how you can specifically grab the promise of individual gets/queries/services but what I have not seen is a way to check that all promises at once have been solved - perhaps by virtue of an Angular state variable that I am just not aware of? Something like:
var currentPromises = $state.promises;
currentPromises.then(thatOneFunction());
Or if not already a built-in variable is there a way to dynamically generate a list of all promises outstanding at the current moment that I can then use .then()? It would assist a lot because I will have many pages in this application that have this structure.
What I have tried/am currently doing"
As of right now I have a work around but it requires some hardcoding that I'd rather not continue. It is based on inserting a call to a function that checks a counter of how many times that function has been called, compares it to how many promises should have called that function already (hardcoded after counting the the promises in the controller) and if it has already gone through all the required promises it then calls thatOneFunction().
Thanks for any help/pointers.
Example bits of my code (real names/variables changed):
Controller
$scope.runAfterAllPromises = function() {
*code dependedent on all promises here*
}
MySvc.loadList(function (results) {
$scope.myList = results;
});
$scope.runAfterAllPromises();
Service
app.service("MySvc", ['$resource', function ($resource) {
this.loadList = function (callBackFunction) {
var stateRes = $resource('/api/ListApi');
stateRes.query(function (ListResults) {
if (callBackFunction && typeof (callBackFunction) === 'function') {
callBackFunction(ListResults)
};
})
You can chain promises, if that helps?
so something like this:
var promises = [];
promises.push(yourPromise1());
promises.push(yourPromise2());
promises.push(yourPromise3());
$q.all(promises).then(function(){ ...});
This what you mean?
Short answer: No, no such object exists.
Now, how to achieve what you want to achieve:
var allPromise = $q.all([promise1, promise2, promiseN]);
allPromise.then(success).catch(error);
Edit: After your "A man can dream comment" I thought to add this:
You could override the $q.defer function, and track all deferred objects created, and get their promises.
Obviously this could have some issues with deferreds created, but never resolved and such, but may be the simplest way to achieve your desired functionality. If you want to go the whole way, you can look at angular decorators which provide a framework of functionality for extending angular providers

JavaScript native Promise execute callback on both results

Is there any way to execute callback on both results of Promise object?
For example I want to make some cleanup logic after execution of xhr request. So I need to do something like this:
var cleanUp = function() { something.here(); }
myLib.makeXhr().then(cleanUp,cleanUp);
In jquery Defered for example i can use method always():
myLib.makeXhr().always(function() { something.here(); });
Does Promise support something like this?
No, there is none. It was discussed but the spec is minimal. It doesn't include a bunch of other functionality. It's designed to interoperate well with library promises and to provide simple functionality.
Here is a correct polyfill of that proposal originally made by StefPanner.
Moreover, I disagree with the current now deleted answers adding it themselves because they're all doing it wrong (as an enumerable property - no fun). Even if we ignore what it does to the return values and error state of the returned promise. The intended way to extend native promises is by subclassing them, sadly, no browsers support this yet so we'll have to wait.
Instead of messing with native prototypes, we should use a different pattern:
openDb().then(foo).then(bar).finally(close).then(more);
Is susceptible to us forgetting to call close, even if we open it 100 times in our app, forgetting to close it even once can still be devastating. On the other hand - we can use the disposer pattern which some promise libraries provide built in ourselves:
openDb(function(db){
return foo(db).then(bar);// chain here
}).then(more);
Basically - this pattern means instead of having openDB return a promise - we have it take a function and return a promise, when the function is run, if it returns a promise we wait for that promise to resolve. It looks something like:
function openDb(withDb){
return justOpenWithoutCleanUp().
then(withDb).
then(clean, function(e){ clean(); throw e; }); // note the rethrow
}
Promise object supports 'always'.
For eg:
var oPromise= jQuery.ajax({
url:YOUR_URL
}).always(function(){
YOUR_CLEAN_UP_METHOD();
})

Handling data that may or may not be a promise

Is there a way to determine if an arbitrary js object is an angular promise? I would like to have different behavior based on whether the result of a function is a promise. I can check to see if the object has a 'then' function, but is there a better way?
Checking for .then() sounds reasonable to me, but you may be looking for $q's when(). This will let you handle everything as though it were a promise so you can normalize the behavior of promise and non promise data.
Here's an example using jQuery's promise api (it's almost identical and easier to setup): Live demo (click).
var deferred = new $.Deferred();
deferred.resolve('some promise data.');
var promise = deferred.promise();
x = 'some regular data.';
foo(x);
foo(promise);
function foo(input) {
$.when(input).then(function(data) {
console.log(data);
});
}

$.Deferred: How to detect when every promise has been executed

I have a number of async tasks that need to be completed, so I'm using promises.
I need to detect when each one of the promises has been executed (both resolved and rejected). I must not continue execution until that point.
I was using something like this:
$.when(promise1, promise2, ...).always();
But this code is wrong, because the when method has lazy evaluation, and it returns as soon as one of the promises fails. So the always callback also runs as soon as one of the promises fail.
I was thinking in coding a workaround, but this use case is so common that maybe somebody has done it already, or maybe there's even a way of doing this using just jQuery (if not, it would be nice to add a Promise.whenNonLazy or a Promise.when(promise1, promise2, ..., false) in the future.
Is this possible?
More sophisticated promise libraries have an allSettled() function like Q or Promise.settle like Bluebird.
In jQuery, you could implement such a function yourself as well and extend the $ namespace with it, but that will only be necessary if you need it often and performance-optimized.
A simpler solution would be to create a new promise for each of the ones you are waiting for, and fulfilling them even when the underlying one is rejected. Then you can use $.when() on them without problems. In short:
// using Underscore's .invoke() method:
$.when.apply(null, _.invoke(promises, "then", null, $.when)).done(…)
More stable:
$.when.apply($, $.map(promises, function(p) {
return p.then(null, function() {
return $.Deferred().resolveWith(this, arguments);
});
})).then(…);
You might change the then callbacks a bit to distinguish between fulfilled and rejected results in the final done.
Smithy,
First let's assume your promises are in an array.
var promises = [....];
What you appear to want is .when() applied to some transform of these promises, such that any rejected promise is converted to resolved, whilst being transparent to promises that are already resolved.
The required operation can be written very succinctly as follows :
$.when.apply(null, $.map(promises, resolvize)).done(...);
//or, if further filtering by .then() is required ...
$.when.apply(null, $.map(promises, resolvize)).then(...);
where resolvize is the transform mechanism.
So what should resolvize(), look like? Let's exploit the characteristics of .then() to make the distinction beteween a resolved and a rejected promise, and respond accordingly.
function resolvize(promise) {
//Note: null allows a resolved promise to pass straight through unmolested;
return promise.then(null, function() {
return $.Deferred().resolve.apply(null, arguments).promise();
});
}
untested
With resolvize in some outer scope, it can be made available to be used in a $.when.apply($.map(promises, resolvize)) expression wherever it is needed. This is most likely adequate, without going to the extent of extending jQuery with a new method.
Regardless of how the transform is achieved, you end up with a potential issue; namely knowing for each argument of the .done() callback, whether its corresponding promise was originally resolved or rejected. That's the price you pay for converting rejection to resolution. You may, however, be able to detect the original status from the parameter(s) with which the original promises were resolved/rejected.
That's an interesting property of always - I hadn't expected that behaviour.
I suppose you could use a master, top-level deferred to monitor the states of the main deferreds, which is resolved only once the main deferreds are all either resolved or rejected. Something like:
//set up master deferred, to observe the states of the sub-deferreds
var master_dfd = new $.Deferred;
master_dfd.done(function() { alert('done'); });
//set up sub-deferreds
var dfds = [new $.Deferred, new $.Deferred, new $.Deferred];
var cb = function() {
if (dfds.filter(function(dfd) {
return /resolved|rejected/.test(dfd.state());
}).length == dfds.length)
master_dfd.resolve();
};
dfds.forEach(function(dfd) { dfd.always(cb); });
//resolve or reject sub-deferreds. Master deferred resolves only once
//all are resolved or rejected
dfds[0].resolve();
dfds[1].reject();
dfds[2].resolve();
Fiddle: http://jsfiddle.net/Wtxfy/3/

Categories