web-dev-qa-db-ja.com

CasperJSのthen()ステートメントで何をラップする必要がありますか?同期/非同期関数の実行順序を決定する方法は?

CasperJSの実行中に非同期であるものとそうでないもの、then()ステートメントでラップする必要があるもの、およびいつ評価されるかを判断するのに苦労しています。

フォールスルーbreakステートメント、変数スコープ、またはevaluate()ステートメントに関係する問題がどこかで発生し、すべてのコードをthen()ステートメントでラップし始めます...問題にならないように。

コードをステップスルーすると、コードが2つのレベルで実行されることに気付きました。それは、コードを解析する評価レベルであり、次にthen()ステートメントがあります。また、私の印刷ステートメントは、説明できない順序で表示されることがあります。

私の質問:これらのthen()ステートメントは実際にどのようにキューに入れられますか?私はドキュメントを読みました、そして私はある程度理解しています。ルールを理解し、何が同期で何が非同期であるかを判断するためのいくつかのカットアンドドライの方法が必要です。

非同期コーディングに関する本の一部を読んだこともありますが、CasperJSの構造を具体的に扱っているものは何もないようです。リソースはありますか?

また、then()ステートメントを配置する場所のベストプラクティスは何ですか?それらは全体にたっぷりとペッパーする必要がありますか、それとも他を呼び出すメインのcasper.begin()関数を制御する必要がありますか?

皆さん、ありがとう。私はPHPに慣れています。

19
Jeremy John

経験則:thenおよびwaitという単語を含むすべてのCasperJS関数は非同期です。このステートメントには多くの例外があります。

then()は何をしていますか?

CasperJSは、スクリプトの制御フローを処理する一連のステップとして構成されています。 then()は、ステップの終了を定義する多くのPhantomJS/SlimerJSイベントタイプを処理します。 then()が呼び出されると、渡された関数は単純なJavaScript配列であるステップキューに入れられます。単純な同期関数であるか、CasperJSが特定のイベントがトリガーされたことを検出したために前のステップが終了した場合、次のステップが実行を開始し、すべてのステップが実行されるまでこれを繰り返します。

これらのステップ関数はすべてcasperオブジェクトにバインドされているため、thisを使用してそのオブジェクトを参照できます。

次の簡単なスクリプトは、2つのステップを示しています。

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

最初のステップは、暗黙の非同期(「ステップ」)open()呼び出しの背後にあるstart()です。 start()関数は、オプションのコールバックも受け取ります。これは、それ自体がこのスクリプトの2番目のステップです。

最初のステップの実行中に、ページが開かれます。ページが完全に読み込まれると、PhantomJSは onLoadFinished event をトリガーし、CasperJSは独自の events をトリガーして、次のステップに進みます。 2番目のステップは単純な完全同期関数であるため、ここでは特別なことは何も起こりません。これが完了すると、実行するステップがなくなるため、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スタイルを何と呼びますか?

ステップを連鎖させる方法のために、それを約束と呼ぶ人もいます。名前(then())とアクションチェーンを除けば、これで類似点は終わりです。 CasperJSのステップチェーンを介してコールバックからコールバックに渡される結果はありません。結果をグローバル変数に格納するか、casperオブジェクトに追加します。その場合、限られたエラー処理しかありません。エラーが発生すると、CasperJSはデフォルト構成で停止します。

run()を呼び出すとすぐに実行が開始され、それ以前のすべての呼び出しはステップをキューに入れるためだけに行われるため、これをBuilderパターンと呼ぶことを好みます(最初の質問を参照)。そのため、ステップ関数の外に同期関数を記述しても意味がありません。簡単に言えば、コンテキストなしで実行されます。ページの読み込みも開始されませんでした。

もちろん、これはビルダーパターンと呼ばれることによる完全な真実ではありません。ステップはネストできます。つまり、別のステップ内でステップをスケジュールすると、現在のステップの後、および現在のステップからすでにスケジュールされている他のすべてのステップの後にキューに入れられます。 (それは多くのステップです!)

次のスクリプトは、私が何を意味するかをよく示しています。

_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()は同期しているため、最初の数字はスクリプト内の同期コードスニペットの位置を示し、2番目の数字は実際に実行/印刷された位置を示します。

重要なポイント:

  • 3番目が最初に来る
  • 番号8は4から5の間に印刷されます

混乱や問題の発見を避けるために、同期関数の後に常に1つのステップで非同期関数を呼び出します。不可能と思われる場合は、複数のステップに分割するか、再帰を検討してください。

waitFor()はどのように機能しますか?

waitFor() は、_wait*_ファミリの中で最も柔軟な関数です。これは、他のすべての関数がこの関数を使用するためです。

waitFor()は、最も基本的な形式でスケジュールします(1つのチェック関数のみを渡し、他には何も渡しません)。渡される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の時点で、経験則に従わない次の追加の非同期関数があります。

キャスパーモジュール:back()forward()reload()repeat()start()withFrame()withPopup()
テスターモジュール: begin()

ソースコード を調べて、特定の関数がthen()またはwait()のどちらを使用しているかわからない場合。

イベントリスナーは非同期ですか?

イベントリスナーはcasper.on(listenerName, callback)を使用して登録でき、casper.emit(listenerName, values)を使用してトリガーされます。 CasperJSの内部に関する限り、それらは非同期ではありません。非同期処理は、それらのemit()呼び出しが存在する関数から行われます。 CasperJSはほとんどのPhantomJSイベントを単純に通過させるため、これらは非同期です。

制御フローから抜け出すことはできますか?

制御または実行フローは、CasperJSがスクリプトを実行する方法です。制御フローから抜け出すときは、2番目(またはそれ以上)のフローを管理する必要があります。これにより、スクリプトの開発と保守性が非常に複雑になります。

例として、どこかで定義されている非同期関数を呼び出したいとします。同期しているという方法で関数を書き直す方法がないと仮定しましょう。

_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();
_

これで、相互に依存する2つのフローができました。

フローを直接分割する他の方法は、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()内に完全なフローをネストすることに固執することをお勧めします。これにより、ファイルごとに複数の完全なテストケースを使用できます。


注:

  • 同期関数/実行について話すとき、私は実際に計算したものを返すことができるブロッキング呼び出しを意味します。
64
Artjom B.