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: )
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
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
Share Improve this question asked Aug 13, 2017 at 14:09 quirimmoquirimmo 9,9883 gold badges32 silver badges45 bronze badges 1- Array.prototype.sort is a synchronous function. – tibetty Commented Aug 13, 2017 at 14:17
3 Answers
Reset to default 17sort
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');
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?
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);