CasperJS 的 then() 语句中必须包含什么?如何确定sync/async函数的执行顺序?
What must be wrapped in then() statements in CasperJS? How to determine execution order of sync/async functions?
在运行使用 CasperJS 时,我很难确定什么是异步的,什么不是异步的,什么必须包含在 then() 语句中,什么时候要评估什么。
我将 运行 解决与 fall-through break 语句、变量范围或 evaluate() 语句有关的问题,然后我将开始包装我的所有代码() 语句...事实证明这不是问题。
我注意到我的代码 运行 在我单步执行时分为两个级别,一个是解析代码的评估级别,然后是 then() 语句。另外,我的打印语句有时以一种莫名其妙的顺序出现。
我的问题:这些 then() 语句实际上是如何排队的?我已经阅读了文档,并且我有点理解。我想了解规则并有一些简单的方法来确定什么是同步什么是异步。
我什至读过一本关于异步编码的书的部分内容,但似乎没有任何内容专门针对 CasperJS 结构。有资源吗?
此外,关于放置 then() 语句的最佳做法是什么?他们应该在整个过程中自由穿插,还是应该在调用其他函数的控制主 casper.begin() 函数中?
谢谢大家,我已经习惯了PHP。
经验法则:所有包含单词 then
和 wait
的 CasperJS 函数都是异步的。这个说法有很多例外。
then()
在做什么?
CasperJS 被组织为一系列处理脚本控制流的步骤。 then()
处理定义步骤结束的许多 PhantomJS/SlimerJS 事件类型。当 then()
被调用时,传递的函数被放入一个简单的 JavaScript 数组的步骤队列中。如果上一步完成,要么是因为它是一个简单的同步函数,要么是因为 CasperJS 检测到触发了特定事件,下一步将开始执行并重复此过程,直到执行完所有步骤。
所有这些步骤函数都绑定到 casper
对象,因此您可以使用 this
.
引用该对象
下面的简单脚本显示了两个步骤:
casper.start("http://example.com", function(){
this.echo(this.getTitle());
}).run();
第一步是在 start()
后面进行隐式异步 ("stepped") open()
调用。 start()
函数还接受一个可选的回调,它本身是此脚本中的第二步。
在第一步的执行过程中,页面被打开。当页面完全加载时,PhantomJS 触发 onLoadFinished
event, CasperJS triggers its own 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 将在默认配置中死亡。
我更喜欢称它为构建器模式,因为一旦你调用 run()
就会开始执行,之前的每个调用都只是将步骤放入队列(参见第一个问题)。这就是为什么在步骤函数之外编写同步函数没有意义。简而言之,它们是在没有任何上下文的情况下执行的。该页面甚至没有开始加载。
当然,将其称为构建器模式并不是全部事实。步骤可以嵌套,这实际上意味着,如果您在另一个步骤中安排一个步骤,它将被放入队列中,排在当前步骤之后,并且排在已经从当前步骤开始安排的所有其他步骤之后。 (这是很多步骤!)
下面的脚本很好地说明了我的意思:
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();
第一个数字显示同步代码片段在脚本中的位置,第二个数字显示实际 executed/printed 位置,因为 echo()
是同步的。
要点:
- 3号在前
- 数字 8 打印在 4 和 5 之间
为避免混淆和难以发现问题,请始终在单步调用同步函数之后调用异步函数。如果觉得不可能,拆分成多个步骤或者考虑递归。
waitFor()
是如何工作的?
waitFor()
是 wait*
家族中最灵活的函数,因为所有其他函数都使用这个函数。
waitFor()
以最基本的形式(只传递一个检查函数,没有其他)安排一个步骤。传递给它的 check
函数被重复调用,直到满足条件或达到(全局)超时。当then
and/or onTimeout
step 函数额外传递时,在这些情况下将调用它。
需要注意的是,如果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()
,因此无需使用这些。
我可以return到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();
什么是测试环境(Tester模块)的异步?
从技术上讲,测试器模块中没有任何东西是异步的。调用 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()
调用不会在多个流之间混合。这使您能够为每个文件使用多个完整的测试用例。
备注:
- 当我谈论 同步 functions/execution 时,我指的是阻塞调用,它实际上可以 return 它计算的东西。
在运行使用 CasperJS 时,我很难确定什么是异步的,什么不是异步的,什么必须包含在 then() 语句中,什么时候要评估什么。
我将 运行 解决与 fall-through break 语句、变量范围或 evaluate() 语句有关的问题,然后我将开始包装我的所有代码() 语句...事实证明这不是问题。
我注意到我的代码 运行 在我单步执行时分为两个级别,一个是解析代码的评估级别,然后是 then() 语句。另外,我的打印语句有时以一种莫名其妙的顺序出现。
我的问题:这些 then() 语句实际上是如何排队的?我已经阅读了文档,并且我有点理解。我想了解规则并有一些简单的方法来确定什么是同步什么是异步。
我什至读过一本关于异步编码的书的部分内容,但似乎没有任何内容专门针对 CasperJS 结构。有资源吗?
此外,关于放置 then() 语句的最佳做法是什么?他们应该在整个过程中自由穿插,还是应该在调用其他函数的控制主 casper.begin() 函数中?
谢谢大家,我已经习惯了PHP。
经验法则:所有包含单词 then
和 wait
的 CasperJS 函数都是异步的。这个说法有很多例外。
then()
在做什么?
CasperJS 被组织为一系列处理脚本控制流的步骤。 then()
处理定义步骤结束的许多 PhantomJS/SlimerJS 事件类型。当 then()
被调用时,传递的函数被放入一个简单的 JavaScript 数组的步骤队列中。如果上一步完成,要么是因为它是一个简单的同步函数,要么是因为 CasperJS 检测到触发了特定事件,下一步将开始执行并重复此过程,直到执行完所有步骤。
所有这些步骤函数都绑定到 casper
对象,因此您可以使用 this
.
下面的简单脚本显示了两个步骤:
casper.start("http://example.com", function(){
this.echo(this.getTitle());
}).run();
第一步是在 start()
后面进行隐式异步 ("stepped") open()
调用。 start()
函数还接受一个可选的回调,它本身是此脚本中的第二步。
在第一步的执行过程中,页面被打开。当页面完全加载时,PhantomJS 触发 onLoadFinished
event, CasperJS triggers its own 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 将在默认配置中死亡。
我更喜欢称它为构建器模式,因为一旦你调用 run()
就会开始执行,之前的每个调用都只是将步骤放入队列(参见第一个问题)。这就是为什么在步骤函数之外编写同步函数没有意义。简而言之,它们是在没有任何上下文的情况下执行的。该页面甚至没有开始加载。
当然,将其称为构建器模式并不是全部事实。步骤可以嵌套,这实际上意味着,如果您在另一个步骤中安排一个步骤,它将被放入队列中,排在当前步骤之后,并且排在已经从当前步骤开始安排的所有其他步骤之后。 (这是很多步骤!)
下面的脚本很好地说明了我的意思:
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();
第一个数字显示同步代码片段在脚本中的位置,第二个数字显示实际 executed/printed 位置,因为 echo()
是同步的。
要点:
- 3号在前
- 数字 8 打印在 4 和 5 之间
为避免混淆和难以发现问题,请始终在单步调用同步函数之后调用异步函数。如果觉得不可能,拆分成多个步骤或者考虑递归。
waitFor()
是如何工作的?
waitFor()
是 wait*
家族中最灵活的函数,因为所有其他函数都使用这个函数。
waitFor()
以最基本的形式(只传递一个检查函数,没有其他)安排一个步骤。传递给它的 check
函数被重复调用,直到满足条件或达到(全局)超时。当then
and/or onTimeout
step 函数额外传递时,在这些情况下将调用它。
需要注意的是,如果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()
,因此无需使用这些。
我可以return到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();
什么是测试环境(Tester模块)的异步?
从技术上讲,测试器模块中没有任何东西是异步的。调用 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()
调用不会在多个流之间混合。这使您能够为每个文件使用多个完整的测试用例。
备注:
- 当我谈论 同步 functions/execution 时,我指的是阻塞调用,它实际上可以 return 它计算的东西。