打破循环/承诺并从函数返回

Break the loop/promise and return from function

本文关键字:函数 返回 承诺 循环      更新时间:2023-09-26

我有以下代码片段。

this.clickButtonText = function (buttonText, attempts, defer) {
    var me = this;
    if (attempts == null) {
        attempts = 3;
    }
    if (defer == null) {
        defer = protractor.promise.defer();
    }
    browser.driver.findElements(by.tagName('button')).then(function (buttons) {
        buttons.forEach(function (button) {
            button.getText().then(
                function (text) {
                    console.log('button_loop:' + text);
                    if (text == buttonText) {
                        defer.fulfill(button.click());
                        console.log('RESOLVED!');
                        return defer.promise;
                    }
                },
                function (err) {
                    console.log("ERROR::" + err);
                    if (attempts > 0) {
                        return me.clickButtonText(buttonText, attempts - 1, defer);
                    } else {
                        throw err;
                    }
                }
            );
        });
    });
    return defer.promise;
};

我的代码不时到达'ERROR::StaleElementReferenceError:陈旧的元素引用:元素未附加到页面文档'行,因此我需要重试并使用"try-1"参数调用我的函数。这是意料之中的行为。但一旦它到达"RESOLVED!"行,它就会不断迭代,所以我看到的smth是这样的:

button_loop:wrong_label_1
button_loop:CORRECT_LABEL
RESOLVED!
button_loop:wrong_label_2
button_loop:wrong_label_3
button_loop:wrong_label_4

问题是:如何在console.log('RESOLVED!')之后打破循环/承诺并从函数返回line?

除了抛出异常之外,没有任何方法可以停止或中断forEach()循环。如果您需要这样的行为,那么forEach()方法是错误的工具,请使用纯循环。

来源:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

出于好奇,你想完成什么?对我来说,你似乎想根据文本点击一个按钮,所以你会在页面上的所有按钮中迭代,直到找到与文本匹配的按钮。

看起来您在非角度页面上使用量角器,如果您在规范文件中使用browser.ignoreSynchronization = true;或在conf.js文件中使用onPrepare块,会更容易,因此您仍然可以利用量角器API,它有两个元素定位器,可以轻松实现这一点。

this.clickButtonText = function(buttonText) {
    return element.all(by.cssContainingText('button',buttonText)).get(0).click();
};

this.clickButtonText = function(buttonText) {
    return element.all(by.buttonText(buttonText)).get(0).click();
};

如果有其他原因想循环浏览按钮,我可以写一个更复杂的解释,使用bluebird来循环浏览元素。它是一个非常有用的解决承诺的库。

您创建了一个额外的延迟对象,这让您自己更加困难。如果单击失败,您可以使用promise本身重试操作。

var clickOrRetry = function(element, attempts) {
    attempts = attempts === undefined ? 3 : attempts;
    return element.click().then(function() {}, function(err) {
        if (attempts > 0) {
            return clickOrRetry(element, attempts - 1);
        } else {
            throw new Error('I failed to click it -- ' + err);
        }
    });
};
return browser.driver.findElements(by.tagName('button')).then(function(buttons) {
    return buttons.forEach(function(button) {
        return clickOrRetry(button);
    });
});

一种方法是(在每次"尝试"时)构建一个承诺链,该承诺链在失败时继续,但在成功时跳到最后。这样的链条将是一般的形式。。。

return initialPromise.catch(...).catch(...).catch(...)...;

并且使用javascript数组方法CCD_ 3以编程方式构造简单。

在实践中,会使代码变得庞大

  • 调用异步CCD_ 4的需要然后执行用于匹配文本的相关联的测试
  • 需要编排3次尝试

但仍然不太笨重。

据我所知,你想要这样的东西:

this.clickButtonText = function (buttonText, attempts) {
    var me = this;
    if(attempts === undefined) {
        attempts = 3;
    }
    return browser.driver.findElements(by.tagName('button')).then(function(buttons) {
        return buttons.reduce(function(promise, button) {
            return promise.catch(function(error) {
                return button.getText().then(function(text) {
                    if(text === buttonText) {
                        return button.click(); // if/when this happens, the rest of the catch chain (including its terminal catch) will be bypassed, and whatever is returned by `button.click()` will be delivered.
                    } else {
                        throw error; //rethrow the "no match" error
                    }
                });
            });
        }, Promise.reject(new Error('no match'))).catch(function(err) {
            if (attempts > 0) {
                return me.clickButtonText(buttonText, attempts - 1); // retry
            } else {
                throw err; //rethrow whatever error brought you to this catch; probably a "no match" but potentially an error thrown by `button.getText()`.
            }
        });
    });
};

注:

  • 使用这种方法,不需要传入延迟对象。事实上,不管你采取什么方法,这都是不好的做法。失败很少是必要的,更不需要四处传递
  • 在reduce构建捕获链之后,我移动了终端catch(... retry ...)块作为最终捕获。这比button.getText().then(onSucccess, onError)结构更有意义,后者会在第一次匹配buttonText失败时导致重试;这在我看来是不对的
  • 您可以将终端捕获进一步下移,以便捕获browser.driver.findElements()抛出的错误(用于重试),尽管这可能有些过头了。如果browser.driver.findElements()失败一次,它很可能会再次失败
  • "重试"策略也可以通过.reduce()过程构建的捕获链的3倍连接来实现。但你会看到一个更大的记忆尖峰
  • 为了清楚起见,我省略了各种console.log(),但它们应该很容易重新插入