JavaScript数组中sort函数实现异步/等待(async/await)的方法

12
我正在尝试在protractor的ElementArrayFinder上实现一个排序方法。众所周知,所有的protractor方法都会返回promises。
因此,我的排序方法有一个取决于promises解决的条件。
为了使它与低于6的node.js版本兼容,我使用了一个名为async/await的node插件。(插件链接: https://www.npmjs.com/package/asyncawait)
以下是我的代码,其中的thisArrayElementFinder:
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');
});

很不幸,执行时间不是我期望的那个。打印:array sorted在比较x.localeCompare(y)的打印之前发生。

你有什么想法我做错了什么?有什么方法可以实现我的目标吗?

非常感谢任何帮助。


Array.prototype.sort 是一個同步函數。 - tibetty
3个回答

17

sort 不接受异步回调函数。它期望返回一个数字值,而不是一个 Promise;并且它返回的是一个数组而不是一个 Promise。没有绕过这一点的方法(尽管可以实现自定义的异步排序算法,甚至是并行的)。

但是这种方式相当低效。在每个排序步骤中进行异步获取比较值会很慢,并且会多次获取每个元素相同的值。不要这样做。相反,使用Schwartzian 变换在排序之前获取要排序的值,然后进行排序(同步),最后使用结果。

你应该这样做:

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');

1
感谢您的回复,Bergi。实际上,您的建议是手动使用异步代码(例如通过promises)检索我的比较值,然后使用同步排序函数返回我之前计算的这些值? - quirimmo
@quirimmo 是的,确切地说就是那样。 - Bergi
这是我目前为止使用 Promises 进行更改的代码。抱歉格式不好,因为在评论区里很糟糕。this.then((elements) => { const arrayOfElementFinders = elements.slice(0, 3); // 或者 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); }); }); - quirimmo
因为不幸的是,在节点<=6中它们默认不可用,所以我想摆脱第三方库。你建议我保留它们并使用这个第三方库吗?我认为切片应该可以正常工作,因为从8个元素中我得到了3个元素。它发生在comparableArraythen内部。我正在进行一些调试以查看发生了什么。再次感谢您的帮助、提示和时间。 - quirimmo
是的,我确认切片运作良好。刚刚测试过了。能否问一下您为什么从map中返回一个包含x.getText()x的数组呢? arrayOfElementFinders.map((x) => [x.getText(), x]) - quirimmo
显示剩余6条评论

1

在V6之前,它不支持Promise吗?

我不喜欢将命令式的await与函数式的promise抽象混合在一起。你不能这样做吗?

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

需要将 .catch() 阶段添加到 ans splatter 中吗?


1
嗨 @Redu,感谢您的回复。 是的,V6支持ES6 Promises,但我不记得从哪个版本开始Node支持es6承诺。 顺便说一句,使用protractor时,您“应该”使用他们对承诺的本机实现:protractor.promise 我正在尝试避免使用ES6 promises,即使它比protractor提供的标准promises好很多。 - quirimmo

1

在用户界面中,当排序功能需要被使用,比如展示两张图片并让用户选择其中一张时,这个函数就会变得非常有用。在这种情况下,只需要反复运行排序功能,直到你获得了所有项目的优先级,这些项目是排序功能需要完全对数组进行排序的。

这个函数可能看起来像这样:

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);
};

为了简单起见(并且为了不可变性),我不修改原始数组,而是返回一个新的数组:


// 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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接