什么必须包含在CasperJS的then()语句中?如何确定同步/异步函数的执行顺序?

时间:2015-05-07 15:08:07

标签: javascript asynchronous casperjs

我正在努力确定什么是异步,什么不是在运行CasperJS时,什么必须包含在then()语句中,以及将在什么时候进行评估。

我会遇到一个问题,这个问题与一个直通破坏语句,变量范围或者evaluate()语句有关,我将开始在then()语句中包装我的所有代码......结果证明不是问题。

我注意到当我单步执行时,我的代码在两个级别上运行,一个解析代码的评估级别,然后是then()语句。此外,我的印刷语句有时会出现无法解释的顺序。

我的问题:这些then()语句如何实际排队?我已经阅读了文档,我有点理解。我想了解这些规则,并有一些切割和干燥的方法来确定什么是同步和什么是异步。

我甚至阅读过关于异步编码的书的部分内容,但似乎并没有真正解决CasperJS结构的问题。有资源吗?

此外,在哪里放置then()语句的最佳做法是什么?它们是应该在整个过程中自由地加上,还是应该在控制主要的casper.begin()函数中调用其他函数?

谢谢大家,我已经习惯了PHP。

1 个答案:

答案 0 :(得分:64)

经验法则: 所有包含单词thenwait的CasperJS函数都是异步的。 此语句有很多例外。

then()在做什么?

CasperJS被组织为一系列处理脚本控制流的步骤。 then()处理许多定义步骤结束的PhantomJS / SlimerJS事件类型。调用then()时,传递的函数将被放入一个简单的JavaScript数组的步骤队列中。如果上一步完成,或者因为它是一个简单的同步函数,或者因为CasperJS检测到触发的特定事件,下一步将开始执行并重复此操作直到执行所有步骤。

所有这些步骤函数都绑定到casper对象,因此您可以使用this引用该对象。

以下简单脚本显示了两个步骤:

casper.start("http://example.com", function(){
    this.echo(this.getTitle());
}).run();

第一步是open()后面的隐式异步("步进")start()调用。 start()函数还接受一个可选的回调,它本身就是这个脚本的第二步。

在执行第一步期间,页面将被打开。页面完全加载后,PhantomJS会触发onLoadFinished event,CasperJS触发自己的events并继续下一步。第二步是一个简单的完全同步功能,所以这里没有任何花哨的东西。完成后,CasperJS退出,因为没有更多步骤可以执行。

此规则有一个例外:当函数传递到run()函数时,它将作为最后一步而不是默认退出执行。如果您没有在那里拨打exit()die(),则需要终止此过程。

then()如何检测下一步必须等待?

以下面的例子为例:

casper.then(function(){
    this.echo(this.getTitle());
    this.fill(...)
    this.click("#search");
}).then(function(){
    this.echo(this.getTitle());
});

如果在步骤执行期间触发了表示加载新页面的事件,则CasperJS将等待页面加载,直到执行下一步。在这种情况下,触发了一个点击,它本身从底层浏览器触发onNavigationRequested event。 CasperJS看到了这一点并使用回调暂停执行,直到加载下一页。其他类型的此类触发器可能是表单提交,或者甚至在客户端JavaScript使用window.open() / window.location执行自己的重定向时。

当然,当我们谈论单页应用程序(使用静态URL)时,这会破坏。 PhantomJS无法检测到例如在单击后呈现不同的模板,因此不能等到它完成加载(这可能需要一些时间从服务器加载数据)。如果以下步骤取决于新页面,则需要使用例如waitUntilVisible()查找要加载的页面唯一的选择器。

您认为这种API风格是什么?

有些人称之为Promises,因为步骤可以链接。除了名称(then())和行动链之外,这是相似之处的结束。没有结果通过CasperJS中的步骤链从回调传递到回调。您可以将结果存储在全局变量中,也可以将其添加到casper对象中。然后只有有限的错误处理。遇到错误时,CasperJS将在默认配置中死亡。

我更喜欢将其称为Builder模式,因为一旦调用run(),执行就会开始,之前的每次调用都只是将步骤放入队列(请参阅第1个问题)。这就是为什么在步进函数之外编写同步函数没有意义。简而言之,它们的执行没有任何背景。该页面甚至没有开始加载。

当然,将其称为构建器模式并不是全部真相。步骤可以嵌套,这实际上意味着如果您在另一个步骤中安排一个步骤,它将在当前步骤之后以及在已经从当前步骤调度的所有其他步骤之后被放入队列中。 (那是很多步骤!)

以下脚本很好地说明了我的意思:

casper.on("load.finished", function(){
    this.echo("1 -> 3");
});
casper.on("load.started", function(){
    this.echo("2 -> 2");
});
casper.start('http://example.com/');
casper.echo("3 -> 1");
casper.then(function() {
    this.echo("4 -> 4");
    this.then(function() {
        this.echo("5 -> 6");
        this.then(function() {
            this.echo("6 -> 8");
        });
        this.echo("7 -> 7");
    });
    this.echo("8 -> 5");
});
casper.then(function() {
    this.echo("9 -> 9");
});
casper.run();

第一个数字显示脚本中同步代码段的位置,第二个数字显示实际执行/打印位置,因为echo()是同步的。

重点:

  • 排在第3位
  • 8号打印在4到5之间

为了避免混淆并且难以发现问题,请始终在同步功能之后一步调用异步函数。如果看起来不可能,请分成多个步骤或考虑递归。

waitFor()如何运作?

waitFor()wait*系列中最灵活的功能,因为每个其他功能都使用此功能。

waitFor()以最基本的形式安排(仅传递一个检查功能而不是其他任何内容)一步。传递给它的check函数会被重复调用,直到满足条件或达到(全局)超时。当另外传递then和/或onTimeout步骤函数时,将在这些情况下调用它。

重要的是要注意,如果waitFor()超时,当你没有传入onTimeout回调函数时,脚本将停止执行,这实际上是一个错误捕获函数:

casper.start().waitFor(function checkCb(){
    return false;
}, function thenCb(){
    this.echo("inner then");
}, null, 1000).then(function() {
    this.echo("outer");
}).run();

还有哪些其他函数也是异步步骤函数?

从1.1-beta3开始,还有以下额外的异步功能,不遵循经验法则:

Casper模块:back()forward()reload()repeat()start()withFrame()withPopup() 测试人员模块:begin()

如果您不确定要查看source code特定功能是否使用then()wait()

事件监听器是异步的吗?

可以使用casper.on(listenerName, callback)注册事件侦听器,并使用casper.emit(listenerName, values)触发它们。就CasperJS的内部而言,它们不是同步的。异步处理来自那些emit()调用所在的函数。 CasperJS简单地通过了大多数PhantomJS事件,因此这是异步的。

我能否突破控制流程?

控制或执行流程是CasperJS执行脚本的方式。当我们打破控制流程时,我们需要管理第二个流程(甚至更多)。这将极大地复杂化脚本的开发和可维护性。

例如,您想要调用在某处定义的异步函数。让我们假设没有办法以这种方式重写函数,它是同步的。

function longRunningFunction(callback) {
    ...
    callback(data);
    ...
}
var result;
casper.start(url, function(){
    longRunningFunction(function(data){
        result = data;
    });
}).then(function(){
    this.open(urlDependsOnFunResult???);
}).then(function(){
    // do something with the dynamically opened page
}).run();

现在我们有两个相互依赖的流程。

直接拆分流的其他方法是使用JavaScript函数setTimeout()setInterval()。由于CasperJS提供waitFor(),因此无需使用它们。

我可以返回CasperJS控制流程吗?

当控制流必须合并回CasperJS流程时,通过设置全局变量并同时等待它设置,有一个明显的解决方案。

示例与上一个问题相同:

var result;
casper.start(url, function(){
    longRunningFunction(function(data){
        result = data;
    });
}).waitFor(function check(){
    return result; // `undefined` is evaluated to `false`
}, function then(){
    this.open(result.url);
}, null, 20000).then(function(){
    // do something with the dynamically opened page
}).run();

测试环境中的异步(测试器模块)是什么?

从技术上讲,测试仪模块中没有任何异步。调用test.begin()只是执行回调。只有当回调本身使用异步代码(意味着test.done()在单个begin()回调中异步调用)时,其他begin()测试用例才能添加到测试用例队列中。

这就是为什么单个测试用例通常包含casper.start()casper.run()的完整导航,而不是相反:

casper.test.begin("description", function(test){
    casper.start("http://example.com").run(function(){
        test.assert(this.exists("a"), "At least one link exists");
        test.done();
    });
});

最好坚持在begin()内嵌套完整的流,因为start()run()来电不会在多个流之间混合。这使您可以为每个文件使用多个完整的测试用例。

备注:

  • 当我谈到同步函数/执行时,我指的是一个阻塞调用,它实际上可以返回它计算的东西。