Escribir tareas asincrónicas en JavaScript moderno

 

 

 

  • Clase magistral de diseño para una interfaz de usuario compleja, con Vitaly Friedman
  • Patrones de diseño de interfaces inteligentes, vídeo de 10h + formación UX

  • Índice
    1. Ejecución sincrónica y patrón de observador
      1. Node.js y emisores de eventos
    2. Promesas y la interminable cadena de devolución de llamadas
      1. Promesas, envoltorios y patrones de cadena
    3. Asíncrono y espera
    4. Conclusión
      1. Otras lecturas

    En este artículo, exploraremos la evolución de JavaScript en torno a la ejecución asincrónica en la era pasada y cómo cambió la forma en que escribimos y leemos código. Comenzaremos con los inicios del desarrollo web y llegaremos hasta ejemplos de patrones asincrónicos modernos.

     

    JavaScript tiene dos características principales como lenguaje de programación, ambas importantes para entender cómo funcionará nuestro código. En primer lugar, es su naturaleza sincrónica , lo que significa que el código se ejecutará línea tras línea, casi como lo lees, y en segundo lugar, que es de un solo subproceso , solo se ejecuta un comando a la vez.

    A medida que el lenguaje evolucionó, aparecieron nuevos artefactos en la escena para permitir la ejecución asincrónica; Los desarrolladores probaron diferentes enfoques mientras resolvían algoritmos y flujos de datos más complicados, lo que llevó al surgimiento de nuevas interfaces y patrones a su alrededor.

    Ejecución sincrónica y patrón de observador

    Como se mencionó en la introducción, JavaScript ejecuta el código que escribe línea por línea, la mayor parte del tiempo. Incluso en sus primeros años, el lenguaje tenía excepciones a esta regla, aunque eran algunas y quizás ya las conozcas: solicitudes HTTP, eventos DOM e intervalos de tiempo.

    const button = document.querySelector('button');// observe for user interactionbutton.addEventListener('click', function(e) { console.log('user click just happened!');})

    Si agregamos un detector de eventos, por ejemplo, el clic de un elemento y el usuario activa esta interacción, el motor JavaScript pondrá en cola una tarea para la devolución de llamada del detector de eventos, pero continuará ejecutando lo que está presente en su pila actual. Una vez que haya terminado con las llamadas presentes allí, ahora ejecutará la devolución de llamada del oyente.

     

    Este comportamiento es similar a lo que sucede con las solicitudes de red y los temporizadores, que fueron los primeros artefactos en acceder a la ejecución asincrónica para los desarrolladores web.

    Aunque estas fueron excepciones de la ejecución sincrónica común en JavaScript, es crucial comprender que el lenguaje sigue siendo de un solo subproceso y, aunque puede poner en cola tareas, ejecutarlas de forma asincrónica y luego volver al subproceso principal, solo puede ejecutar una pieza de código. a la vez.

    Por ejemplo, veamos una solicitud de red.

    var request = new XMLHttpRequest();request.open('GET', '//some.api.at/server', true);// observe for server responserequest.onreadystatechange = function() { if (request.readyState === 4 request.status === 200) { console.log(request.responseText); }}request.send();

    Cuando el servidor regresa, onreadystatechangese pone en cola una tarea para el método asignado (la ejecución del código continúa en el hilo principal).

    Nota : Explicar cómo los motores JavaScript ponen en cola tareas y manejan subprocesos de ejecución es un tema complejo de abordar y probablemente merezca un artículo propio. Aún así, recomiendo ver “¿ Qué diablos es el bucle de eventos de todos modos?” ” de Phillip Roberts para ayudarle a comprender mejor.

    En cada caso mencionado, estamos respondiendo a un evento externo. Un determinado intervalo de tiempo alcanzado, una acción del usuario o una respuesta del servidor. No pudimos crear una tarea asincrónica per se, siempre observamos sucesos que sucedían fuera de nuestro alcance.

    Es por eso que el código con esta forma se llama Patrón de Observador , que en este caso está mejor representado por la addEventListenerinterfaz. Pronto florecieron las bibliotecas de emisores de eventos o los marcos que exponían este patrón.

    Node.js y emisores de eventos

    Un buen ejemplo es Node.js, cuya página se describe a sí misma como "un tiempo de ejecución de JavaScript asincrónico impulsado por eventos", por lo que los emisores de eventos y las devoluciones de llamadas eran ciudadanos de primera clase. Incluso EventEmitterya tenía un constructor implementado.

    const EventEmitter = require('events');const emitter = new EventEmitter();// respond to eventsemitter.on('greeting', (message) = console.log(message));// send eventsemitter.emit('greeting', 'Hi there!');

    Este no era solo el enfoque para la ejecución asincrónica, sino también un patrón central y una convención de su ecosistema. Node.js abrió una nueva era en la escritura de JavaScript en un entorno diferente, incluso fuera de la web. Como consecuencia, eran posibles otras situaciones asincrónicas, como crear nuevos directorios o escribir archivos.

     

    const { mkdir, writeFile } = require('fs');const styles = 'body { background: #ffdead; }';mkdir('./assets/', (error) = { if (!error) { writeFile('assets/main.css', styles, 'utf-8', (error) = { if (!error) console.log('stylesheet created'); }) }})

    Es posible que observe que las devoluciones de llamada reciben un errorcomo primer argumento; si se esperan datos de respuesta, se utilizan como segundo argumento. Esto se llamó Patrón de devolución de llamada de error primero , que se convirtió en una convención que los autores y contribuyentes adoptaron para sus propios paquetes y bibliotecas.

    Promesas y la interminable cadena de devolución de llamadas

    A medida que el desarrollo web enfrentó problemas más complejos que resolver, apareció la necesidad de mejores artefactos asincrónicos. Si miramos el último fragmento de código, podemos ver un encadenamiento de devolución de llamada repetido que no escala bien a medida que aumenta el número de tareas.

    Por ejemplo, agreguemos solo dos pasos más: lectura de archivos y preprocesamiento de estilos.

    const { mkdir, writeFile, readFile } = require('fs');const less = require('less')readFile('./main.less', 'utf-8', (error, data) = { if (error) throw error less.render(data, (lessError, output) = { if (lessError) throw lessError mkdir('./assets/', (dirError) = { if (dirError) throw dirError writeFile('assets/main.css', output.css, 'utf-8', (writeError) = { if (writeError) throw writeError console.log('stylesheet created'); }) }) })})

    Podemos ver cómo a medida que el programa que estamos escribiendo se vuelve más complejo, el código se vuelve más difícil de seguir para el ojo humano debido al encadenamiento de devoluciones de llamadas múltiples y al manejo repetido de errores.

    Promesas, envoltorios y patrones de cadena

    Promisesno recibieron mucha atención cuando se anunciaron por primera vez como la nueva incorporación al lenguaje JavaScript, no son un concepto nuevo ya que otros lenguajes tenían implementaciones similares décadas antes. La verdad es que cambiaron mucho la semántica y la estructura de la mayoría de los proyectos en los que trabajé desde su aparición.

    Promisesno solo introdujo una solución integrada para que los desarrolladores escribieran código asincrónico, sino que también abrió una nueva etapa en el desarrollo web que sirvió como base de construcción de nuevas características posteriores de la especificación web como fetch.

    Migrar un método de un enfoque de devolución de llamada a uno basado en promesas se volvió cada vez más habitual en proyectos (como bibliotecas y navegadores), e incluso Node.js comenzó a migrar lentamente a ellos.

    Por ejemplo, ajustemos readFileel método de Node:

    const { readFile } = require('fs');const asyncReadFile = (path, options) = { return new Promise((resolve, reject) = { readFile(path, options, (error, data) = { if (error) reject(error); else resolve(data); }) });}

    Aquí ocultamos la devolución de llamada ejecutándola dentro de un constructor Promise, llamando resolvecuando el resultado del método es exitoso y rejectcuando se define el objeto de error.

     

    Cuando un método devuelve un Promiseobjeto, podemos seguir su resolución exitosa pasándole una función then, su argumento es el valor que se resolvió la promesa, en este caso data,.

    Si se produjo un error durante el método, catchse llamará a la función, si está presente. Trucos de los Sims 4

    Nota : si necesita comprender más en profundidad cómo funcionan las Promesas, le recomiendo el artículo “ JavaScript Promises: An Introducción ” de Jake Archibald que escribió en el blog de desarrollo web de Google.

    Ahora podemos utilizar estos nuevos métodos y evitar cadenas de devolución de llamadas.

    asyncRead('./main.less', 'utf-8') .then(data = console.log('file content', data)) .catch(error = console.error('something went wrong', error))

    Tener una forma nativa de crear tareas asincrónicas y una interfaz clara para realizar un seguimiento de sus posibles resultados permitió a la industria salir del patrón Observer. Los basados ​​en promesas parecían resolver el código ilegible y propenso a errores.

    Como un mejor resaltado de sintaxis o mensajes de error más claros ayudan durante la codificación, un código que es más fácil de razonar se vuelve más predecible para el desarrollador que lo lee, y una mejor imagen de la ruta de ejecución es más fácil de detectar un posible error.

    PromisesLa adopción fue tan global en la comunidad que Node.js lanzó rápidamente versiones integradas de sus métodos de E/S para devolver objetos Promise, como importarles operaciones de archivos desde fs.promises.

    Incluso proporcionó una promisifyutilidad para envolver cualquier función que siguiera el patrón de devolución de llamada de error primero y transformarla en una basada en promesa.

    ¿Pero las Promesas ayudan en todos los casos?

    Reimaginemos nuestra tarea de preprocesamiento de estilo escrita con Promises.

    const { mkdir, writeFile, readFile } = require('fs').promises;const less = require('less')readFile('./main.less', 'utf-8') .then(less.render) .then(result = mkdir('./assets') .then(() = writeFile('assets/main.css', result.css, 'utf-8')) ) .catch(error = console.error(error))

    Hay una clara reducción de la redundancia en el código, especialmente en torno al manejo de errores en el que confiamos ahora catch, pero Promises de alguna manera no logró entregar una sangría de código clara que se relacione directamente con la concatenación de acciones.

    En realidad, esto se logra en la primera thendeclaración después de que readFilese llama. Lo que pasa después de estas líneas es la necesidad de crear un nuevo alcance donde primero podamos hacer el directorio, para luego escribir el resultado en un archivo. Esto provoca una ruptura en el ritmo de sangría, lo que no facilita la determinación de la secuencia de instrucciones a primera vista.

    Una forma de resolver esto es crear previamente un método personalizado que maneje esto y permita la concatenación correcta del método, pero estaríamos introduciendo una profundidad más de complejidad a un código que ya parece tener lo que necesita para lograr la tarea. queremos.

     

    Nota : Tenga en cuenta que este es un programa de ejemplo, tenemos control sobre algunos de los métodos y todos siguen una convención de la industria, pero ese no es siempre el caso. Con concatenaciones más complejas o la introducción de una biblioteca con una forma diferente, nuestro estilo de código puede romperse fácilmente.

    Con mucho gusto, la comunidad de JavaScript aprendió nuevamente de las sintaxis de otros lenguajes y agregó una notación que ayuda mucho en estos casos en los que la concatenación de tareas asincrónicas no es tan agradable o sencilla de leer como lo es el código sincrónico.

    Asíncrono y espera

    A Promisese define como un valor no resuelto en el momento de la ejecución y la creación de una instancia de a Promisees una llamada explícita de este artefacto.

    const { mkdir, writeFile, readFile } = require('fs').promises;const less = require('less')readFile('./main.less', 'utf-8') .then(less.render) .then(result = mkdir('./assets') .then(() = { writeFile('assets/main.css', result.css, 'utf-8') })) .catch(error = console.error(error))

    Dentro de un método asíncrono, podemos usar la awaitpalabra reservada para determinar la resolución de a Promiseantes de continuar su ejecución.

    Repasemos un fragmento de código usando esta sintaxis.

    const { mkdir, writeFile, readFile } = require('fs').promises;const less = require('less')async function processLess() { const content = await readFile('./main.less', 'utf-8') const result = await less.render(content) await mkdir('./assets') await writeFile('assets/main.css', result.css, 'utf-8')}processLess()

    Nota : Tenga en cuenta que necesitábamos mover todo nuestro código a un método porque await hoy no podemos usarlo fuera del alcance de una función asíncrona.

    Cada vez que un método asíncrono encuentre una awaitdeclaración, dejará de ejecutarse hasta que se resuelva el valor o la promesa del procedimiento.

    Hay una consecuencia clara del uso de la notación async/await: a pesar de su ejecución asincrónica, el código parece sincrónico , que es algo a lo que los desarrolladores estamos más acostumbrados a ver y razonar.

    ¿Qué pasa con el manejo de errores? Para ello utilizamos afirmaciones que están presentes desde hace mucho tiempo en el idioma, tryy catch​​.

    const { mkdir, writeFile, readFile } = require('fs').promises;const less = require('less');async function processLess() { try { const content = await readFile('./main.less', 'utf-8') const result = await less.render(content) await mkdir('./assets') await writeFile('assets/main.css', result.css, 'utf-8') } catch(e) { console.error(e) }}processLess()

    Tenemos la seguridad de que cualquier error que se produzca en el proceso será manejado por el código dentro de la catchdeclaración. Tenemos un lugar central que se encarga del manejo de errores, pero ahora tenemos un código que es más fácil de leer y seguir.

    Tener acciones consiguientes que devuelven el valor no necesita almacenarse en variables como mkdiresa no rompe el ritmo del código; Tampoco es necesario crear un nuevo alcance para acceder al valor de resulten un paso posterior.

     

    Es seguro decir que las promesas fueron un artefacto fundamental introducido en el lenguaje, necesario para habilitar la notación asíncrona/en espera en JavaScript, que puede usar tanto en los navegadores modernos como en las últimas versiones de Node.js.

    Nota : Recientemente en JSConf, Ryan Dahl, creador y primer colaborador de Node, lamentó no haber cumplido con Promises en su desarrollo inicial, principalmente porque el objetivo de Node era crear servidores controlados por eventos y administración de archivos para los cuales el patrón Observer servía mejor.

    Conclusión

    La introducción de Promises en el mundo del desarrollo web cambió la forma en que ponemos en cola las acciones en nuestro código y cambió la forma en que razonamos sobre la ejecución de nuestro código y cómo creamos bibliotecas y paquetes.

    Pero alejarse de las cadenas de devolución de llamadas es más difícil de resolver. Creo que tener que pasar un método no thennos ayudó a alejarnos del hilo de pensamiento después de años de estar acostumbrados al patrón Observer y a los enfoques adoptados por los principales proveedores en la comunidad como Node.js.

    Como dice Nolan Lawson en su excelente artículo sobre usos incorrectos en concatenaciones de promesas , ¡ los viejos hábitos de devolución de llamadas son difíciles de eliminar ! Más adelante explica cómo escapar de algunos de estos peligros.

    Creo que las promesas eran necesarias como un paso intermedio para permitir una forma natural de generar tareas asincrónicas, pero no nos ayudaron mucho a avanzar en mejores patrones de código; a veces, en realidad, se necesita una sintaxis de lenguaje más adaptable y mejorada.

    A medida que intentamos resolver acertijos más complejos usando JavaScript, vemos la necesidad de un lenguaje más maduro y experimentamos con arquitecturas y patrones que no estábamos acostumbrados a ver antes en la web.

    Todavía no sabemos cómo se verá la especificación ECMAScript dentro de años, ya que siempre estamos ampliando la gobernanza de JavaScript fuera de la web e intentando resolver acertijos más complicados.

    Es difícil decir ahora qué necesitaremos exactamente del lenguaje para que algunos de estos acertijos se conviertan en programas más simples, pero estoy contento con cómo la web y el propio JavaScript están moviendo las cosas, tratando de adaptarse a los desafíos y nuevos entornos. Siento que JavaScript es ahora un lugar más asincrónico y amigable que cuando comencé a escribir código en un navegador hace más de una década.

    Otras lecturas

    • “ Promesas de JavaScript: una introducción ”, Jake Archibald
    • “ Promise Anti-Patterns ”, documentación de la biblioteca Bluebird
    • " Tenemos un problema con las promesas ", Nolan Lawson

    (dm, il)Explora más en

    • javascript
    • Codificación





    Tal vez te puede interesar:

    1. ¿Deberían abrirse los enlaces en ventanas nuevas?
    2. 24 excelentes tutoriales de AJAX
    3. 70 técnicas nuevas y útiles de AJAX y JavaScript
    4. Más de 45 excelentes recursos y repositorios de fragmentos de código

    Escribir tareas asincrónicas en JavaScript moderno

    Escribir tareas asincrónicas en JavaScript moderno

    Clase magistral de diseño para una interfaz de usuario compleja, con Vitaly Friedman Patrones de diseño de interfaces inteligentes, vídeo de 10h + formaci�

    programar

    es

    https://aprendeprogramando.es/static/images/programar-escribir-tareas-asincronicas-en-javascript-moderno-1005-0.jpg

    2024-05-21

     

    Escribir tareas asincrónicas en JavaScript moderno
    Escribir tareas asincrónicas en JavaScript moderno

    Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

     

     

    Top 20