2009-10-15 15 views
13

Me estoy escribiendo mucho espagueti en Javascript cuando tengo que lidiar con aplicaciones asíncronas (especialmente cuando se trata de código OpenSocial donde se deben obtener todos los datos a través de JS) . El patrón habitual es algo así como:Cómo evitar el código de espagueti en Javascript

  1. El usuario inicia sesión en la aplicación por primera vez, obtiene sus datos.
  2. Haz una A con sus datos (por ejemplo, consigue a sus amigos enviando una solicitud al servidor).
  3. Haz B con estos datos (por ejemplo, envía a sus amigos al servidor para que realicen algún procesamiento).
  4. Haz C sobre sus datos (por ejemplo, comprueba que la respuesta del servidor sea válida para que podamos hacer otra cosa).

Tenga en cuenta que esta ruta de ejecución secuencial (1 => 2 => 3 => 4) no encaja bien con la asincrónica. naturaleza de Ajax por lo que el usuario termina esperando durante mucho tiempo y el código se convierte en un lío ya que cada paso depende de los anteriores.

Un ejemplo de código:

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

Se puede ver que cada función depende de la anterior, y lo que es peor, tiene que ser llamado en un orden específico para ser útil. Se pone peor cuando tiene que agregar cosas como mostrar u ocultar las pantallas de carga y otros trucos mientras el usuario espera.

¿Cómo vas a arreglar esto?

+6

Cocínelo hasta que esté al dente –

+2

Su principal preocupación parece ser que el usuario termina esperando por mucho tiempo para que todo esto se complete.¿Por qué no proporcionar comentarios al usuario después de que cada solicitud se devuelva para que sepan que algo está sucediendo? Podría ser tan simple como una barra de progreso de 3 etapas o mensajes que le digan al usuario lo que está sucediendo. (Incluso podría lanzar un mensaje de "splines de reticulación" para meme apeal.) –

+0

Sam: Estoy mostrando pantallas de carga en este momento con un lindo robot agitando sus brazos, pero el código también me preocupa porque sé que tendré algunos problemas para leerlo en un par de meses. –

Respuesta

4

Una opción podría ser tener una variable que muestre el estado actual, y tener una función de "controlador" que siempre sea la función de devolución de llamada AJAX. En función del estado actual, la función del controlador llamará a la siguiente función en línea. Para simplificar la función del controlador, probablemente almacenara la secuencia de funciones para llamar a un objeto Javascript, de modo que lo único que hace la función del controlador es buscar y pasar a la próxima función de la secuencia. Este enfoque podría ser facilitado por tener un solo objeto Javascript que siempre es el parámetro de la función (y contiene todos los datos que devolvió las llamadas AJAX anteriores

Ejemplo:.

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

Construye tu javascript cliente con una arquitectura MVC

+1

javascriptMVC - ¡Supongo que deberían hacer que sus enlaces funcionen en IE8! – ScottE

+1

Agregaría Backbone (http://documentcloud.github.com/backbone/) y Spine (http://spinejs.com/) como marcos MVC de clientes livianos. – jbandi

1

El código para manejar las funciones de mostrar, ocultar y estado se debe extraer en las funciones. Entonces, para evitar el "spaghettiness", una solución es usar en línea las funciones anónimas.

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

Esto es lo que estaba haciendo al principio, pero lamentablemente la API OpenSocial JS es demasiado detallada. Puede ver cómo se verían las líneas 2 a 4 de su ejemplo en el sistema operativo aquí: http://gist.github.com/211062. Parte de ese código se puede abstraer en funciones, pero todavía tendría el mismo problema de saltar como un loco. –

0

Si desea que sus funciones sean capaces de operar independientemente, equipar a los asíncronos con devoluciones de llamada genérico en lugar de llamadas a la función "siguiente" en la fila. Luego, como dijo JacobM, configure un "controlador" que los llame en secuencia. He modificado el código de ejemplo siguiente para demostrar (cuidado, esto no ha sido probado):

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

devoluciones de llamada Creación ponen un poco difícil si usted no está familiarizado con ellos - he aquí una explicación decente: http://pixelpushing.net/2009/04/anonymous-function-callbacks/. Si quieres ser más complejo (de nuevo, como sugirió JacobM), podrías escribir un código que maneje esto automáticamente: dale una lista de funciones y las ejecuta en orden, pasando los datos de devolución de llamada. Práctico, pero puede ser excesivo para sus necesidades.

2

Puede controlar este tipo de espagueti con cosas como el Observer pattern. Algunos frameworks de JavaScript tienen una implementación lista para usar de esta funcionalidad, por ejemplo, las funciones de publicación/suscripción Dojo's.

1

Parte del problema es que usted está pensando en esto como un proceso de cuatro pasos con tres viajes redondos al servidor requerido. Si realmente crees que se trata de un flujo de trabajo único, y lo más probable es que el usuario haga, entonces la mejor aceleración es recolectar la mayor cantidad posible de información en la primera interacción para reducir los viajes de ida y vuelta. Esto puede incluir permitir que el usuario marque una casilla diciendo que quiere seguir este camino, para que no tenga que regresar al usuario entre pasos, o permitirle ingresar una conjetura sobre los nombres de los amigos para que pueda procesar el primera vez, o precargando la lista de nombres la primera vez.

La forma en que ha descrito el código funciona mejor si esta es solo una de las muchas rutas que puede seguir el usuario; los múltiples viajes de ida y vuelta son necesarios porque en cada interacción, estás descubriendo lo que el usuario quiere y una respuesta diferente te habría enviado en una dirección diferente. Aquí es cuando realmente brilla el estilo de código débilmente acoplado que está menospreciando. Parece que cada paso está desconectado de lo que sucedió antes porque las acciones del usuario están impulsando la actividad.

Por lo tanto, la verdadera pregunta es si el usuario tiene una opción al principio que determina la dirección de su código. Si no es así, entonces quiere optimizar la ruta que sabe (o predecir fuertemente) que la interacción va a continuar. Por otro lado, si las interacciones del usuario conducen el proceso, desacoplando los pasos y reaccionando a cada interacción es lo correcto, pero esperaría muchas más posibilidades divergentes.

Cuestiones relacionadas