【问题标题】:Break the loop/promise and return from function打破循环/承诺并从函数返回
【发布时间】:2016-07-21 09:11:16
【问题描述】:

我有以下代码 sn-p。

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: stale element reference: element is not attach to the page document' 行,所以我需要再次尝试并使用 “尝试 - 1” 参数。这是预期的行为。 但是一旦它到达 "RESOLVED!" 行,它就会不断迭代,所以我看到这样的东西:

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!'); 行之后打破循环/承诺并从函数返回?

【问题讨论】:

  • 你试过使用.done回调吗?
  • 否,但似乎检查我的共享延迟是否已解决对我有用。将等待更优雅的答案,如果没有,将提供我自己的

标签: javascript promise protractor


【解决方案1】:

除了抛出异常之外,没有其他方法可以停止或中断 forEach() 循环。如果您需要这样的行为,forEach() 方法是错误的工具,请改用普通循环。

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

【讨论】:

  • 这不是 forEach 而是 button.getText() 继续执行。所以我需要打破量角器的流程
【解决方案2】:

出于好奇,您想完成什么?对我来说,您似乎想根据其文本单击按钮,因此您将遍历页面上受尝试次数限制的所有按钮,直到找到文本匹配项。

看起来您在非角度页面上使用量角器如果您在规范文件中使用 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 的库。

【讨论】:

    【解决方案3】:

    创建一个额外的延迟对象会让你自己变得更难。如果单击失败,您可以使用 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);
        });
    });
    

    【讨论】:

      【解决方案4】:

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

      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() 进程构建的catch 链的3 次连接来实现。但您会看到更大的内存峰值。
      • 为了清楚起见,我省略了各种 console.log(),但它们应该很容易重新注入。

      【讨论】:

        猜你喜欢
        • 2018-09-29
        • 1970-01-01
        • 2014-03-12
        • 2017-10-21
        • 2015-10-07
        • 2017-04-05
        • 1970-01-01
        • 2017-07-26
        相关资源
        最近更新 更多