2009-10-15 16 views
13

Mi sto ritrovando a scrivere un sacco di spaghetti in Javascript quando ho a che fare con applicazioni asincrone (specialmente quando si tratta di codice OpenSocial dove tutti i dati devono essere ottenuti tramite JS) . Il solito schema è qualcosa del tipo:Come evitare il codice spaghetti in Javascript

  1. L'utente accede all'applicazione per la prima volta, ottiene i suoi dati.
  2. Fare A sui suoi dati (ad esempio, ottenere i suoi amici inviando una richiesta al server).
  3. Fare B su questi dati (ad esempio, inviare i propri amici al server per l'elaborazione).
  4. Fare C sui propri dati (ad esempio controllare che la risposta del server sia valida in modo che possiamo fare qualcos'altro).

Si noti che questo percorso di esecuzione sequenziale (1 => 2 => 3 => 4) non si adatta bene all'async. natura di Ajax quindi l'utente finisce per aspettare a lungo e il codice si trasforma in un casino dato che ogni passo dipende da quelli precedenti.

Un esempio con il codice:

gadgets.util.registerOnLoadHandler(setupUser()) 
... 
function setupUser() { 
    var req = [get data and setup request] 
    req.send(some_url, some_data, function(response) { getFriendsFor(response.user) }); 
} 

function getFriendsFor(user) { 
    var friends = [get friends from user] 
    var req = [setup request] 
    req.send(some_other_url, some_other_data, function(response { validateFriendsResponse(response.friends) }); 
} 

function validateFriendsResponse(friends) { 
    if (friends.valid()) 
    ... 
    loadCanvas(); 
} 

Si può vedere che ogni funzione dipende quella precedente, e quel che è peggio, deve essere chiamato in un ordine specifico per essere utile. Diventa peggio quando devi aggiungere elementi come mostrare/nascondere il caricamento degli schermi e altri espedienti mentre l'utente è in attesa.

Come procederesti a risolvere questo problema?

+6

Cuocilo fino a quando non è al dente –

+2

La tua preoccupazione principale sembra essere che l'utente finisce per aspettare molto tempo per completare tutto questo.Perché non fornire un feedback all'utente dopo che ciascuna richiesta viene restituita in modo che sappia che qualcosa sta accadendo. Potrebbe essere semplice come una barra di avanzamento a 3 fasi o messaggi che dicono all'utente cosa sta succedendo. (Potresti anche inserire un messaggio di "spionette reticolari" per meme apeal.) –

+0

Sam: Sto mostrando gli schermi di caricamento in questo momento con un simpatico robot che agita le braccia, ma il codice mi preoccupa anche perché so che ne avrò un po ' problemi di leggerlo in un paio di mesi. –

risposta

4

Un'opzione potrebbe essere quella di avere una variabile che mostra lo stato corrente e avere una funzione "controller" che è sempre la funzione di callback AJAX. In base allo stato corrente, la funzione controller chiamerà la prossima funzione in linea. Per semplificare la funzione del controller, probabilmente memorizzerei la sequenza di funzioni per chiamare un oggetto Javascript, quindi tutta la funzione del controller è una ricerca e un passaggio alla funzione successiva nella sequenza. Questo approccio potrebbe essere facilitato da avere un unico oggetto Javascript che è sempre il parametro della funzione (e contiene tutte i dati che è stato restituito dalle chiamate AJAX precedenti

Esempio:.

var user = {}; 
var currentState = 1; 

var someFunction = function(user) {//stuff here that adds data to user via AJAX, advances currentState, and calls controllerFunction as callback}; 
var someOtherFunction = function(user) {//stuff here that does other things to user, advances currentState, and calls controllerFunction as callback} 

var functionSequence = {1:someFunction, 2:someOtherFunction} 

var controllerFunction = function() { 
    //retrieve function from functionSequence based on current state, and call it with user as parameter 
} 
1

Costruisci la tua javascript client con un'architettura MVC

+1

javascriptMVC - Immagino che dovrebbero far funzionare i loro collegamenti su IE8! – ScottE

+1

Aggiungerei Backbone (http://documentcloud.github.com/backbone/) e Spine (http://spinejs.com/) come framework MVC client leggeri. – jbandi

1

Il codice per gestire le funzioni di visualizzazione, occultamento e stato deve essere estratto nelle funzioni. Quindi, per evitare "spaghettiness", una soluzione consiste nell'utilizzare le funzioni anonime in linea.

function setupUser() { 
    var req = [get data and setup request] 
    req.send(some_url, some_data, function(response) { 
    var friends = [get friends from user] 
    var req = [setup request] 
    req.send(some_other_url, some_other_data, function(response {   
     if (friends.valid()) 
     ... 
     loadCanvas(); 
    }); 
    }); 
} 
+0

Questo è ciò che stavo facendo inizialmente, ma purtroppo l'API JS di OpenSocial è troppo prolissa. Puoi vedere quali sono le linee 2-4 del tuo esempio in OS qui: http://gist.github.com/211062. Alcuni di quel codice possono essere astratti nelle funzioni, ma avrebbe comunque lo stesso problema di saltare in giro come un pazzo. –

0

Se volete che il vostro funzioni per essere in grado di operare in modo indipendente, dotare quelli asincroni con callback generici al posto di chiamate alla funzione "prossimo" in linea. Quindi, come ha detto JacobM, impostare un "controller" che li chiamerà in sequenza. Ho modificato il codice di esempio per dimostrare (attenzione, questo non è stato testato):

gadgets.util.registerOnLoadHandler(userSetupController()) 
... 
function setupUser(callback) { 
    var req = [get data and setup request] 
    req.send(some_url, some_data, function(response) { callback(response.user) }); 
} 

function getFriendsFor(user,callback) { 
    var friends = [get friends from user] 
    var req = [setup request] 
    req.send(some_other_url, some_other_data, function(response { callback(response.friends) }); 
} 

function validateFriendsResponse(friends) { 
    if (friends.valid()) 
    return true; 
    else 
    return false; 
} 

function userSetupController() { 
    setupUser(function(user){ 
     getFriendsFor(user,function(friends){ 
      if (validateFriendsResponse(friends)) { 
       loadCanvas(); 
      } else { 
       // don't load the canvas? 
      } 
     }); 
    }); 
} 

Creazione di callback ottenere un po 'difficile se non hai familiarità con loro - ecco una spiegazione decente: http://pixelpushing.net/2009/04/anonymous-function-callbacks/. Se vuoi diventare più complesso (di nuovo, come suggerito da JacobM), potresti scrivere del codice che lo gestisce automaticamente - dargli un elenco di funzioni, ed eseguirle in ordine, passando i dati di callback in giro. Conveniente, ma potrebbe essere eccessivo per le tue esigenze.

2

È possibile controllare questo tipo di spaghetti con cose come lo Observer pattern. Alcuni framework JavaScript hanno un'implementazione pronta all'uso di questa funzionalità, ad esempio le funzioni di pubblicazione/sottoscrizione Dojo's.

1

Parte del problema è che stai pensando a questo come un processo in quattro fasi con tre round trip sul server richiesto. Se pensi davvero che questo sia un singolo flusso di lavoro e la cosa che l'utente è più propenso a fare, la migliore velocità è quella di raccogliere quante più informazioni possibili nella prima interazione possibile per ridurre i round trip. Ciò può includere la possibilità di consentire all'utente di selezionare una casella in cui si dice di voler seguire questo percorso, in modo da non dover tornare all'utente tra una procedura e l'altra, o consentirle di inserire un'ipotesi sui nomi degli amici che è possibile elaborare prima volta o pre-caricando la lista dei nomi per la prima volta.

Il modo in cui hai delineato il codice funziona meglio se questo è solo uno dei molti percorsi che l'utente potrebbe seguire; i viaggi di andata e ritorno multipli sono necessari perché ad ogni interazione, stai scoprendo ciò che l'utente vuole, e una risposta diversa ti avrebbe mandato in una direzione diversa. Questo è quando lo stile di codice liberamente abbinato che stai denigrando brilla davvero. Sembra che ogni fase sia disconnessa da ciò che è accaduto prima perché le azioni dell'utente stanno guidando l'attività.

Quindi, la vera domanda è se l'utente ha una scelta all'inizio che determina la direzione del codice. In caso contrario, quindi si desidera ottimizzare il percorso che si conosce (o prevedere fortemente) l'interazione sta per andare. D'altra parte, se le interazioni dell'utente guidano il processo, disaccoppiare i passaggi e reagire a ciascuna interazione è la cosa giusta, ma ci si aspetterebbe molte più possibilità divergenti.

Problemi correlati