12

I am trying to implement a sort method on protractor ElementArrayFinder. As known, all protractor methods return promises. So my sort method has a condition which depends on promises resolution. I am using a node plugin for async/await in order to make it compatible with node.js versions lower than 6. (Here the plugin: https://www.npmjs.com/package/asyncawait)

Here my code, where this is the ArrayElementFinder:

var asyncCompare = async(function(a, b) {
    let x = await (a.getText());
    let y = await (b.getText());
    console.log(x.localeCompare(y));
    return x.localeCompare(y);
});

var sortTheArray = async(function(arrayOfElementFinders) {
    return await (arrayOfElementFinders.sort(asyncCompare));
});

this.then((elements) => {
    let arrayOfElementFinders = [elements[0], elements[1], elements[2]];
    let sortedArray = sortTheArray(arrayOfElementFinders);
    console.log('array sorted');
});

Unfortunately the timing execution is not the one I expect. The print: array sorted happens before than the prints of comparing x.localeCompare(y). Any idea what am I doing wrong? And any idea how to achieve my objective?

Thanks a lot for any help

1
  • Array.prototype.sort is a synchronous function. Commented Aug 13, 2017 at 14:17

3 Answers 3

17

sort does not take an asynchronous callback. It expects a numeric value as the return value, not a promise for one; and it does return an array not a promise. There's no way around this (though one could implement a custom asynchronous sorting algorithm - even a parallel one).

But that's pretty inefficient anyway. Doing asynchronous fetching of compare values in every sort step will be slow - and it will fetch the same value per element multiple times. Don't do that. Instead, use a Schwartzian transform to fetch the values to sort by before, then sort (synchronously), then use the results.

You should do

const elements = await this;
const arrayOfElementFinders = elements.slice(0, 3); // or Array.from?
const comparableArray = await Promise.all(arrayOfElementFinders.map(async x => [await x.getText(), x]));
comparableArray.sort((a, b) => +(a[0] > b[0]) || -(a[0] < b[0]));
const sortedArray = comparableArray.map(x => x[1]);
console.log('array sorted');
Sign up to request clarification or add additional context in comments.

11 Comments

Thanks for your reply Bergi. So actually your suggestion is to retrieve my comparing values manually using async code (through promises for example) and then use a sync sort function which returns these values I computed before?
@quirimmo Yes, exactly that
Thanks a lot for your spot and your answer, really nice one and really useful. Unfortunately I cannot get it work. Two main issues: 1) at the moment I am getting back 3 undefined from the code above: [ undefined, undefined, undefined ] . 2) I would like to get rid of async/await and just use promises, and maybe the third party library I am using for the implementation is the reason of the first issue too. Any idea please? Thanks a lot again
This is what I did so far for changing it with promises. Sorry for the bad formatting but here in the comments is awful. this.then((elements) => { const arrayOfElementFinders = elements.slice(0, 3); // or Array.from? const comparableArray = Promise.all(arrayOfElementFinders.map(async((x) => [await(x.getText(), x)]))); comparableArray.then((arr) => { arr.sort((a, b) => +(a[0] > b[0]) || -(a[0] < b[0])); const sortedArray = arr.map(x => x[1]); console.log(sortedArray); }); });
Where do the undefineds happen, in arrayOfElementFinders (maybe my slice() call doesn't work), or comparableArray or in sortedArray? Regarding async/await, you can of course replace them with then() callbacks, but I don't see any reason to do that when they're available.
|
1

Does it not support promises before V6?

I dislike mixing imperative await with functional promise abstraction. Wouldn't you be able to do like?

Promise.all([Promise.resolve(10), Promise.resolve(4), Promise.resolve(7)])
       .then(r => r.sort((a,b) => a-b))
       .then(s => console.log(s));

ans splatter .catch() stages as needed?

1 Comment

Hi @Redu thanks for your reply. Yes it does support ES6 Promises in the V6, but I don't remember since which version node supports es6 promises. Btw using protractor you "should" use their native implementation of promises: protractor.promise I am trying to avoid to use ES6 promises, even if so much better than the standard ones provided by protractor
1

I can see the use of this in cases where the sort function is user-facing and someone is, for example, being shown two pictures and has to pick one over another. In this case it's possible to just re-run the sort function over and over again until you have the precedence of all items the sort-function needs to fully sort your array.

This function could look something like this:

type Box<T> = {  value: T };

const sortAsync = async <T>(
    arr: T[],
    cmp: (a: T, b: T) => Promise<number>
): Promise<T[]> => {

    // Keep a list of two values and the precedence
    const results: [Box<T>, Box<T>, number][] = [];

    // Box the value in case you're sorting primitive values and some
    // values occur more than once.
    const result: Box<T>[] = arr.map(value => ({ value }));

    for (; ;) {
        let nextA: Box<T>, nextB: Box<T>, requiresSample = false;

        result.sort((a, b) => {

            // Check if we have to get the precedence of a value anyways,
            // so we can skip this one.
            if (requiresSample) return 0;

            // See if we already now which item should take precedence
            const match = results.find(v => v[0] === a && v[1] === b);
            if (match) return match[2];

            // We need the precedence of these two elements
            nextA = a;
            nextB = b;
            requiresSample = true;
            return 0;
        });

        if (requiresSample) {
            // Let the async function calculate the next value
            results.push([nextA, nextB, await cmp(nextA.value, nextB.value)]);
        } else break; // It's fully sorted
    }

    // Map the sorted boxed-value array to its containing value back again
    return result.map(v => v.value);
};

For simplicity (and the sake of immutability) I don't modify the original array and just return a new one:


// Create new array and shuffle the values in it
const values = new Array(20)
    .fill(0)
    .map((_, index) => index)
    .sort(() => Math.random() > 0.5 ? 1 : -1);

console.log('Start: ', values);

const sorted = await sortAsync(values, async (a, b) => {
    // Simulate an async task by waiting some time
    await new Promise(resolve => setTimeout(resolve, Math.random() * 25));
    return a === b ? 0 : a > b ? 1 : -1;
});

console.log('End: ', sorted);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.