2015-05-07 13 views
17

Sto avendo qualche difficoltà nel determinare cosa sia asincrono e cosa non lo sia mentre è in esecuzione CasperJS, cosa deve essere racchiuso tra istruzioni then() e cosa verrà valutato quando.Cosa deve essere racchiuso tra istruzioni then() in CasperJS? Come determinare l'ordine di esecuzione delle funzioni di sincronizzazione/asincrono?

Mi imbatterò in un problema da qualche parte che ha a che fare con un'istruzione break-through break, scope variabile o l'istruzione evaluate(), e inizierò a racchiudere tutto il mio codice in istruzioni then(). . che risulta non essere il problema

Ho notato che il mio codice viene eseguito su due livelli quando lo passo, un livello di valutazione che analizza il codice e poi vengono le istruzioni then(). Inoltre, le mie dichiarazioni di stampa appaiono in un ordine a volte inspiegabile.

La mia domanda: in che modo queste istruzioni then() vengono effettivamente accodate? Ho letto i documenti, e in qualche modo ho capito. Voglio capire le regole e avere alcuni modi pratici per determinare cosa è la sincronizzazione e cosa è asincrona.

Ho persino letto parti di un libro sulla codifica asincrona, ma in realtà niente sembra riguardare specificamente la struttura di CasperJS. Qualche risorsa?

Inoltre, quali sono le migliori pratiche su dove inserire le istruzioni then()? Dovrebbero essere disseminati liberamente, o dovrebbero essere nella principale funzione di controllo casper.begin() che chiama gli altri?

Grazie gente, sono abituato al PHP.

+0

Sempre, volevo scrivere una risposta canonica per questo, ma ci vorrà del tempo. –

+0

Sarebbe una bella risposta, @ArtjomB. I libri asincroni sembrano affrontare un diverso tipo di framework rispetto a CasperJS. Tutti i post che ho letto tramite Google riguardano come scrivere attorno alle normali funzioni asincrone. In realtà non so quale sia il tipo _type_ di procedura che gestisce il framework di CaspersJS del codice, quindi non posso davvero googlearlo. Anche quello sarebbe utile. So che CasperJS implementa le Promesse, ma i capitoli su questo non aiutano davvero la codifica in CasperJS. E CasperJS è ben documentato, ma gli esempi sono semplici. –

risposta

60

regola empirica: Tutti CasperJS funzioni che contengono le parole then e wait sono asincrona. Questa dichiarazione ha molte eccezioni.

Cosa fa then()?

CasperJS è organizzato come una serie di passaggi che gestiscono il flusso di controllo del tuo script. then() gestisce i molti tipi di eventi PhantomJS/SlimerJS che definiscono la fine di un passo. Quando viene chiamato then(), la funzione passata viene inserita in una coda di passaggi che è semplicemente una matrice JavaScript. Se il passaggio precedente è terminato, sia perché si trattava di una semplice funzione sincrona o perché CasperJS ha rilevato eventi specifici in cui è stato attivato, il passaggio successivo inizierà l'esecuzione e lo ripeterà fino a quando non verranno eseguiti tutti i passaggi.

Tutte queste funzioni di passo sono legate all'oggetto casper, quindi è possibile fare riferimento a tale oggetto utilizzando this.

Il semplice script seguente mostra due fasi:

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

Il primo passo è un asincrono implicita ("passo passo") open() chiamata dietro start(). La funzione start() accetta anche una richiamata opzionale che è di per sé il secondo passo in questo script.

Durante l'esecuzione del primo passaggio, la pagina viene aperta. Quando la pagina è completamente caricata, PhantomJS attiva lo onLoadFinished event, CasperJS attiva il proprio events e continua con il passaggio successivo. Il secondo passo è una semplice funzione completamente sincrona, quindi qui non sta succedendo nulla di stravagante. Al termine, CasperJS si chiude, perché non ci sono più passaggi da eseguire.

Esiste un'eccezione a questa regola: quando una funzione viene passata alla funzione run(), verrà eseguita come ultima operazione invece dell'uscita predefinita. Se non si chiama exit() o die(), è necessario interrompere la procedura.

In che modo then() rileva che il passaggio successivo deve attendere?

Prendiamo ad esempio il seguente esempio:

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

Se durante l'esecuzione di un passo viene attivato un evento che denota il caricamento di una nuova pagina, quindi CasperJS attende il caricamento della pagina fino a quando l'esecuzione del passo successivo. In questo caso è stato attivato un clic che a sua volta ha attivato uno onNavigationRequested event dal browser sottostante. CasperJS vede questo e sospende l'esecuzione utilizzando i callback fino a quando non viene caricata la pagina successiva. Altri tipi di trigger possono essere invii di moduli o anche quando il client JavaScript esegue qualcosa come il proprio reindirizzamento con window.open()/window.location.

Naturalmente, questo si interrompe quando si parla di applicazioni a pagina singola (con un URL statico). PhantomJS non è in grado di rilevare che, ad esempio, un modello diverso viene sottoposto a rendering dopo un clic e quindi non può attendere il completamento del caricamento (potrebbe richiedere del tempo quando i dati vengono caricati dal server). Se i seguenti passaggi dipendono dalla nuova pagina, dovrai utilizzare ad es. waitUntilVisible() per cercare un selettore unico per la pagina da caricare.

Come si chiama questo stile API?

Alcune persone lo chiamano Promesse, a causa del modo in cui i passaggi possono essere concatenati. A parte il nome (then()) e una catena d'azione, questa è la fine delle somiglianze. Non vi è alcun risultato passato da richiamata a richiamata attraverso la catena di passaggi in CasperJS. O si memorizza il risultato in una variabile globale o lo si aggiunge all'oggetto casper. Quindi c'è solo una gestione degli errori limitata. Quando si verifica un errore, CasperJS morirà nella configurazione predefinita.

Preferisco chiamarlo modello Builder, perché l'esecuzione inizia non appena si chiama run() e ogni chiamata precedente è presente solo per mettere passaggi in coda (vedere 1a domanda). Ecco perché non ha senso scrivere funzioni sincrone al di fuori delle funzioni di passo. In poche parole, vengono eseguiti senza alcun contesto. La pagina non ha nemmeno iniziato a caricare.

Ovviamente non è tutta la verità definendola come un modello di costruzione. I passaggi possono essere nidificati, il che significa che se si pianifica un passaggio all'interno di un altro passaggio, questo verrà inserito nella coda dopo il passaggio corrente e dopo tutti gli altri passaggi già pianificati dal passaggio corrente. (Questo è un sacco di passi!)

Lo script che segue è un buon esempio di ciò che intendo:

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

Il primo numero indica la posizione del frammento di codice sincrono nello script e la seconda spettacoli la posizione effettiva eseguita/stampata, perché echo() è sincrono.

punti importanti:

  • numero 3 viene prima
  • numero 8 viene stampato tra il 4 e il 5

Per evitare confusione e difficile da trovare problemi, sempre chiamare le funzioni asincrone dopo la funzioni sincrone in un unico passaggio. Se sembra impossibile, dividi in più passaggi o considera la ricorsione.

Come funziona waitFor()?

waitFor() è la funzione più flessibile nella famiglia wait*, poiché ogni altra funzione utilizza questo.

waitFor() orari nella sua forma più semplice (passando solo una funzione di controllo e nient'altro) un passo. La funzione check che viene passata in esso, viene chiamata più volte fino a quando la condizione non viene soddisfatta o viene raggiunto il timeout (globale). Quando una funzione di passaggio then e/o onTimeout viene inoltrata in aggiunta, verrà richiamata in questi casi.

E 'importante notare che se waitFor() timeout, lo script si fermerà l'esecuzione quando non avete superato nella funzione di callback onTimeout che è essenzialmente una funzione di errore cattura:

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

Quali sono le altre funzioni che sono anche funzioni passo asincrone?

Al 1.1-beta3 vi sono le seguenti funzioni asincrone aggiuntive che non seguono la regola empirica:

Casper modulo: back(), forward(), reload(), repeat(), start(), withFrame(), withPopup()
modulo Tester : begin()

Se non si è sicuri esaminare source code se una funzione specifica utilizza then() o wait().

Gli ascoltatori di eventi sono asincroni?

Gli ascoltatori di eventi possono essere registrati utilizzando casper.on(listenerName, callback) e verranno attivati ​​utilizzando casper.emit(listenerName, values). Per quanto riguarda gli interni di CasperJS, non sono asincroni. La gestione asincrona deriva dalle funzioni in cui si trovano le chiamate emit(). CasperJS passa la maggior parte degli eventi PhantomJS semplicemente attraverso, quindi è qui che quelli sono asincroni.

Posso uscire dal flusso di controllo?

Il controllo o il flusso di esecuzione è il modo in cui CasperJS esegue lo script. Quando interrompiamo il flusso di controllo, dobbiamo gestire un secondo flusso (o anche più). Ciò complicherà immensamente lo sviluppo e la manutenibilità dello script.

Ad esempio, si desidera chiamare una funzione asincrona definita da qualche parte. Supponiamo che non ci sia modo di riscrivere la funzione in questo modo, che sia sincrona.

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

Ora abbiamo due flussi che dipendono l'uno dall'altro.

Altri modi per suddividere il flusso direttamente utilizzando le funzioni JavaScript setTimeout() e setInterval(). Poiché CasperJS fornisce waitFor(), non è necessario utilizzarli.

Posso tornare al flusso di controllo di CasperJS?

Quando un flusso di controllo deve essere reimmesso nel flusso CasperJS, esiste una soluzione ovvia impostando una variabile globale e contemporaneamente aspettando che venga impostata.

esempio è la stessa come nella domanda precedente:

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

Qual è asincrona nell'ambiente di prova (modulo Tester)?

Tecnicamente, nulla è asincrono nel modulo tester. Chiamando test.begin() si esegue semplicemente la richiamata. Solo quando lo stesso callback utilizza il codice asincrono (ovvero test.done() viene chiamato in modo asincrono all'interno di una singola callback begin()), gli altri casi di test begin() possono essere aggiunti alla coda del caso di test.

Questo è il motivo per cui un singolo caso di test di solito consiste in una navigazione completa con casper.start() e casper.run() e non viceversa:

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

E 'meglio attenersi a nidificare un flusso completo all'interno di begin(), dal momento che la Le chiamate start() e run() non verranno missate tra più flussi. Ciò consente di utilizzare più casi di test completi per file.


Note:

  • Quando parlo di sincrono funzioni/esecuzione, voglio dire una chiamata di blocco che può effettivamente restituire la cosa che calcola.
+2

Grazie, una buona panoramica di casperjs. Potresti voler sottolineare che le chiamate "allora" di Casper sono completamente diverse dalle promesse, anche se usano lo stesso nome di metodo. Questo modello di builder non è chiamato "promesse" solo perché è possibile concatenare i metodi 'then' che accettano i callback. – Bergi

+1

Bella risposta completa! – Fanch

+5

È la mia risposta più lunga fino ad oggi, quindi è meglio che sia completa;) @Fanch –