Validación basada en la promesa

El concepto de "Promesas" ha cambiado la forma en que escribimos JavaScript asíncrono. Durante el año pasado, muchos marcos han incorporado alguna forma del patrón de Promesa para hacer que el código asíncrono sea más fácil de escribir, leer y mantener. Por ejemplo, jQuery agregó $ .Deferred (), y NodeJS tiene los módulos Q y jspromise que funcionan tanto en el cliente como en el servidor. Los frameworks MVC del lado del cliente, como EmberJS y AngularJS, también implementan sus propias versiones de Promises.

Pero no tiene que detenerse allí: podemos repensar soluciones antiguas y aplicar Promesas a ellas. En este artículo, haremos exactamente eso: validar un formulario usando el patrón de Promesa para exponer una API súper simple.


Que es una promesa?

Las promesas notifican el resultado de una operación..

En pocas palabras, Promesas notifica el resultado de una operación. El resultado puede ser un éxito o un fracaso, y la operación, en sí misma, puede ser cualquier cosa que se ajuste a un simple contrato. Elegí usar la palabra contrato Porque puedes diseñar este contrato de varias maneras diferentes. Afortunadamente, la comunidad de desarrollo llegó a un consenso y creó una especificación llamada Promesas / A+.

Sólo la operación realmente sabe cuándo se ha completado; como tal, es responsable de notificar su resultado utilizando el contrato Promesas / A +. En otras palabras, promesas para decirle el resultado final en la finalización.

La operación devuelve un promesa objeto, y puede adjuntar sus devoluciones de llamada utilizando el hecho() o fallar() metodos La operación puede notificar su resultado llamando promesa.resolver () o promesa.reyecto (), respectivamente. Esto se representa en la siguiente figura:


Uso de promesas para la validación de formularios

Déjame pintar un escenario plausible.

Podemos repensar soluciones antiguas y aplicarles promesas..

La validación de formularios del lado del cliente siempre comienza con las intenciones más simples. Usted puede tener un formulario de registro con Nombre y Email y necesita asegurarse de que el usuario proporciona una entrada válida para ambos campos. Eso parece bastante sencillo, y empiezas a implementar tu solución.

A continuación, se le indica que las direcciones de correo electrónico deben ser únicas y que decide validar la dirección de correo electrónico en el servidor. Por lo tanto, el usuario hace clic en el botón enviar, el servidor verifica la singularidad del correo electrónico y la página se actualiza para mostrar cualquier error. Ese parece ser el enfoque correcto, ¿verdad? No Tu cliente quiere una experiencia de usuario ingeniosa; los visitantes deben ver los mensajes de error sin actualizar la página.

Tu formulario tiene la Nombre campo que no requiere ningún soporte del lado del servidor, pero luego tiene el Email campo que requiere que usted haga una solicitud al servidor. Medios de peticiones de servidor $ .ajax () llamadas, por lo que tendrá que realizar la validación de correo electrónico en su función de devolución de llamada. Si su formulario tiene varios campos que requieren soporte del lado del servidor, su código será un lío anidado de $ .ajax () Llamadas en devoluciones de llamada. Devolución de llamadas dentro de devoluciones de llamada: "¡Bienvenido al infierno de devolución de llamada! ¡Esperamos que tenga una estancia miserable!".

Entonces, ¿cómo manejamos el infierno de devolución de llamada?

La solución que prometí

Da un paso atrás y piensa en este problema. Tenemos un conjunto de operaciones que pueden tener éxito o fallar. Cualquiera de estos resultados puede ser capturado como Promesa, y las operaciones pueden ser desde simples verificaciones del lado del cliente hasta validaciones complejas del lado del servidor. Las promesas también le brindan el beneficio adicional de consistencia, además de permitirle evitar el control condicional del tipo de validación. Veamos como podemos hacer esto..

Como señalé anteriormente, hay varias implementaciones de promesa en la naturaleza, pero me enfocaré en la implementación de la Promesa $ .Deferred () de jQuery.

Construiremos un marco de validación simple donde cada verificación devuelva inmediatamente un resultado o una Promesa. Como usuario de este framework, solo tienes que recordar una cosa: "Siempre devuelve una Promesa". Empecemos.

Validator Framework utilizando Promesas

Creo que es más fácil apreciar la simplicidad de Promises desde el punto de vista del consumidor. Digamos que tengo un formulario con tres campos: Nombre, Correo electrónico y Dirección:

 

Primero configuraré los criterios de validación con el siguiente objeto. Esto también sirve como la API de nuestro marco:

 var validationConfig = '.name': cheques: 'required', campo: 'Nombre', '.email': cheques: ['required'], campo: 'Email', '.address':  verifica: ['aleatorio', 'requerido'], campo: 'Dirección';

Las claves de este objeto de configuración son los selectores jQuery; Sus valores son objetos con las siguientes dos propiedades:

  • cheques: una cadena o matriz de validaciones.
  • campo: el nombre del campo legible, que se usará para informar errores en ese campo

Podemos llamar a nuestro validador, expuesto como la variable global. V, Me gusta esto:

 V.validate (validationConfig) .done (function () // Success) .fail (function (errors) // Validations error. Errors tiene los detalles);

Tenga en cuenta el uso de la hecho() y fallar() devoluciones de llamada estas son las devoluciones de llamada predeterminadas para entregar el resultado de una promesa. Si añadimos más campos de formulario, simplemente podría aumentar el validationConfig Objeto sin perturbar el resto de la configuración (el principio abierto-cerrado en acción). De hecho, podemos agregar otras validaciones, como la restricción de unicidad para las direcciones de correo electrónico, al extender el marco del validador (que veremos más adelante).

Así que esa es la API orientada al consumidor para el marco de validación. Ahora, vamos a sumergirnos y ver cómo funciona bajo el capó.

Validador, bajo el capó

El validador se expone como un objeto con dos propiedades:

  • tipo: contiene los diferentes tipos de validaciones, y también sirve como punto de extensión para agregar más.
  • validar: el método central que realiza las validaciones en función del objeto de configuración proporcionado.

La estructura general se puede resumir como:

 var V = (función ($) var validador = / * * Punto de extensión: solo agregue a este hash * * V.tipo ['mi-validador'] = * ok: función (valor) devolver verdadero; , * mensaje: 'Mensaje de error para mi validador' * * / type: 'required': ok: function (value) // is valid?, message: 'Este campo es obligatorio', ... , / ** * * @param config * * '': cuerda | objeto | [cadena] * * / validate: function (config) // 1. Normalice el objeto de configuración // 2. Convierta cada validación a una promesa // 3. Envuelva en una promesa maestra // 4. Devuelva la promesa maestra ; ) (jQuery);

los validar El método proporciona los fundamentos de este marco. Como se ve en los comentarios anteriores, hay cuatro pasos que ocurren aquí:

1. Normalizar el objeto de configuración..

Aquí es donde pasamos por nuestro objeto de configuración y lo convertimos en una representación interna. Esto es principalmente para capturar toda la información que necesitamos para llevar a cabo la validación y reportar errores si es necesario:

 función normalizeConfig (config) config = config || ; validaciones var = []; $ .each (config, function (selector, obj) // hacer una matriz para la comprobación simplificada var checks = $ .isArray (obj.checks)? obj.checks: [obj.checks]; $ .each (check, function (idx, check) validations.push (control: $ (selector), check: getValidator (check), checkName: check, campo: obj.field););); validaciones de retorno;  la función getValidator (type) if ($ .type (type) === 'string' && validator.type [type]) devuelve validator.type [type]; devolver validator.noCheck; 

Este código recorre las claves en el objeto de configuración y crea una representación interna de la validación. Utilizaremos esta representación en el validar método.

los getValidator () ayudante recupera el objeto validador de la tipo picadillo. Si no encontramos uno, devolvemos el sin verificación validador que siempre devuelve verdadero.

2. Convertir cada validación a una Promesa..

Aquí, nos aseguramos de que cada validación sea una Promesa al verificar el valor de retorno de validation.ok (). Si contiene el entonces() método, sabemos que es una Promesa (esto es según la especificación de Promesas / A +). Si no, creamos una Promesa ad-hoc que se resuelve o rechaza en función del valor de retorno.

 validate: function (config) // 1. Normalice el objeto de configuración config = normalizeConfig (config); promesas var = [], cheques = []; // 2. Convierta cada validación a una promesa $ .each (config, function (idx, v) var value = v.control.val (); var retVal = v.check.ok (value); // Make a promesa, la verificación se basa en Promesas / A + espec. if (retVal.then) promises.push (retVal); else var p = $ .Deferred (); if (retVal) p.resolve (); else p.reject (); promises.push (p.promise ()); checks.push (v);); // 3. Envolverse en una promesa maestra // 4. Devolver la promesa maestra

3. Envolver en una promesa principal.

Creamos una serie de Promesas en el paso anterior. Cuando todos tengan éxito, queremos resolver una vez o fallar con información detallada sobre los errores. Podemos hacer esto envolviendo todas las Promesas en una Promesa única y propagando el resultado. Si todo va bien, simplemente resolvemos la promesa maestra..

Para errores, podemos leer de nuestra representación de validación interna y utilizarla para informar. Dado que puede haber múltiples fallos de validación, hacemos un bucle sobre el promesas arregla y lee el estado() resultado. Recopilamos todas las promesas rechazadas en el ha fallado matriz y llamada rechazar() en la promesa maestra:

 // 3. Envolverse en una promesa maestra var masterPromise = $ .Deferred (); $ .when.apply (null, promises) .done (function () masterPromise.resolve ();) .fail (function () var failed = []; $ .each (promises, function (idx, x) if (x.state () === 'rechazado') var failedCheck = cheques [idx]; var error = check: failedCheck.checkName, error: failedCheck.check.message, campo: failedCheck.field, control: failedCheck.control; failed.push (error);); masterPromise.reject (failed);); // 4. Devolver la promesa de promesa de devolución masterPromise.promise ();

4. Devolver la promesa maestra.

Finalmente volvemos la promesa maestra de la validar() método. Esta es la Promesa en la que el código del cliente configura el hecho() y fallar() devoluciones de llamada.

Los pasos dos y tres son el quid de este marco. Al normalizar las validaciones en una Promesa, podemos manejarlas consistentemente. Tenemos más control con un objeto Master Promise, y podemos adjuntar información contextual adicional que puede ser útil para el usuario final.


Usando el Validador

Vea el archivo de demostración para un uso completo del marco validador. Usamos el hecho() devolución de llamada para informar de éxito y fallar() para mostrar una lista de errores contra cada uno de los campos. Las capturas de pantalla a continuación muestran los estados de éxito y fracaso:

La demostración utiliza la misma configuración de validación y HTML mencionada anteriormente en este artículo. La única adición es el código que muestra las alertas. Tenga en cuenta el uso de la hecho() y fallar() devoluciones de llamada para manejar los resultados de validación.

 función showAlerts (errores) var alertContainer = $ ('. alert'); $ ('. error'). remove (); si (! errores) alertContainer.html ('Todos pasaron');  else $ .each (errores, función (idx, err) var msg = $ ('') .addClass (' error ') .text (err.error); err.control.parent (). append (msg); );  $ ('. validate'). click (function () $ ('. indicator'). show (); $ ('. alert'). empty (); V.validate (validationConfig) .done (function () $ ('. indicador'). hide (); showAlerts ();) .fail (función (errores) $ ('. indicador'). hide (); showAlerts (errores);); );

Extendiendo el Validador

Anteriormente mencioné que podemos agregar más operaciones de validación al marco extendiendo el validador tipo picadillo. Considera el aleatorio validador como ejemplo. Este validador tiene éxito al azar o falla Sé que no es un validador útil, pero vale la pena señalar algunos de sus conceptos:

  • Utilizar setTimeout () Para hacer la validación asíncrona. También puedes pensar en esto como una simulación de latencia de red..
  • Devolver una promesa de la De acuerdo() método.
 // Amplíe con un validador aleatorio V.tipo ['aleatorio'] = ok: function (value) var aplazado = $ .Deferred (); setTimeout (function () var result = Math.random () < 0.5; if (result) deferred.resolve(); else deferred.reject(); , 1000); return deferred.promise(); , message: 'Failed randomly. No hard feelings.' ;

En la demo, utilicé esta validación en el Dirección campo así

 var validationConfig = / * cilpped por brevedad * / '.address': cheques: ['random', 'required'], campo: 'Address';

Resumen

Espero que este artículo le haya dado una buena idea de cómo puede aplicar Promesas a problemas antiguos y crear su propio marco alrededor de ellos. El enfoque basado en la promesa es una solución fantástica para operaciones abstractas que pueden o no ejecutarse de forma síncrona. También puede encadenar devoluciones de llamada e incluso componer promesas de orden superior a partir de un conjunto de otras promesas.

El patrón de Promesa es aplicable en una variedad de escenarios, y con suerte encontrarás algunos de ellos y verás una coincidencia inmediata!


Referencias

  • Promesas / A + espec.
  • jQuery.Deferred ()
  • Q
  • jspromise