alecxe alecxe - 5 months ago 92
AngularJS Question

Spreading promises in Protractor

library has this neat feature to resolve and spread multiple promises into separate arguments:


If you have a promise for an array, you can use spread as a
replacement for then. The spread function “spreads” the values over
the arguments of the fulfillment handler.


return getUsername()
.then(function (username) {
return [username, getUser(username)];
})
.spread(function (username, user) {

});


In protractor, we are trying to use the built-in
protractor.promise
coming from
WebDriverJS
.

The Question:

Is it possible to have the "spread" functionality with
protractor.promise
?

Example use case:

We've implemented a custom jasmine matcher to check if an element is focused. Here we need to resolve two promises before making an equality comparison. Currently, we are using
protractor.promise.all()
and
then()
:

protractor.promise.all([
elm.getId(),
browser.driver.switchTo().activeElement().getId()
]).then(function (values) {
jasmine.matchersUtil.equals(values[0], values[1]);
});


which ideally we'd like to have in a more readable state:

protractor.promise.all([
elm.getId(),
browser.driver.switchTo().activeElement().getId()
]).spread(function (currentElementID, activeElementID) {
return jasmine.matchersUtil.equals(currentElementID, activeElementID);
})

Answer

It may come a bit ugly to use, but you can define an independent helper function, which can be passed to then() as a parameter and have a callback, which is usually passed to then() to be passed to it. This function will then convert array value to function arguments:

protractor.promise.all([
    elm.getId(),
    browser.driver.switchTo().activeElement().getId()
]).then(spread(function (currentElementID, activeElementID) {
    // ---^^^----- use helper function to spread args
    jasmine.matchersUtil.equals(currentElementID, activeElementID);
}));


// helper function gets a callback
function spread(callback) {
    // and returns a new function which will be used by `then()`
    return function (array) {
        // with a result of calling callback via apply to spread array values
        return callback.apply(null, array);
    };
}

You can still chain it with another then() and provide rejection callbacks; it keeps all the behavior of Protractor promises the same, but just converts array of values to arguments.

Drawbacks are that it is does not have a perfect look like in your example (not .all().spread() but .all().then(spread()) ) and you'll probably have to create a module for this helper or define it globally to be able to use it easily in multiple test files.

Update:

With ES2015 it is possible to use destructuring assignment along with then():

protractor.promise.all([
    elm.getId(),
    browser.driver.switchTo().activeElement().getId()
]).then(function (values) {
    // Destructure values to separate variables
    const [currentElementID, activeElementID] = values; 
    jasmine.matchersUtil.equals(currentElementID, activeElementID);
}));