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

时间:2016-07-21 09:11:16

标签: javascript promise protractor

我有以下代码段。

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:陈旧元素引用:元素没有附加到页面文档'行,所以我需要再次尝试并使用“尝试 - 1”参数。这是预期的行为。 但是一旦它达到“已解决!”一行,它就会继续迭代,所以我看到这样的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!'); 行之后从函数返回?

4 个答案:

答案 0 :(得分:0)

除了抛出异常之外,没有办法停止或中断forEach()循环。如果你需要这样的行为,forEach()方法是错误的工具,而是使用普通循环。

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

答案 1 :(得分:0)

出于好奇,你想要完成什么?对我而言,您似乎想要根据它的文本点击一个按钮,这样您就可以遍历页面上的所有按钮,这些按钮受到一个尝试号码的限制,直到您找到该文本的匹配项。

看起来您在非角度页面上使用量角器,如果您在规范文件中使用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循环遍历这些元素。它是一个非常有用的解决承诺的库。

答案 2 :(得分:0)

您通过创建额外的延迟对象使自己变得更加困难。如果点击失败,您可以使用承诺本身重试操作。

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

答案 3 :(得分:0)

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

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

...并且使用javascript数组方法.reduce()以编程方式构建很简单。

在实践中,代码将变得笨重:

  • 需要调用异步button.getText(),然后执行相关的测试以匹配文本,
  • 需要协调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链之后,我将终端catch(... retry ...)块移动到最后一个catch。这比button.getText().then(onSucccess, onError)结构更有意义,这会导致第一次失败时重试与buttonText匹配;这对我来说似乎不对。
  • 您可以进一步向下移动终端捕获,以便browser.driver.findElements()引发的错误被捕获(重试),尽管这可能是过度杀伤。如果browser.driver.findElements()失败一次,它可能会再次失败。
  • “重试”策略可以通过.reduce()过程构建的捕获链的3x级联来实现。但是你会看到更大的内存峰值。
  • 为了清楚起见,我省略了各种console.log(),但它们应该很容易重新注入。