Código de contador asíncrono. PHP asincrónico: ¿por qué? Traducción

Declaraciones de variables y funciones, pero para convertir esta característica de JS en un problema, hay que esforzarse mucho. El código JavaScript síncrono sólo tiene un serio inconveniente: por sí solo no le llevará muy lejos.

Casi todos los programas JS útiles se escriben utilizando métodos de desarrollo asincrónicos. Aquí es donde entran en juego las funciones de devolución de llamada, o “devoluciones de llamada” en el lenguaje común. Aquí usamos “promesas”, u objetos de promesa, generalmente llamados promesas. Aquí puede encontrar generadores y construcciones asíncronas/en espera. El código asincrónico, en comparación con el código síncrono, suele ser más difícil de escribir, leer y mantener. A veces se convierte en estructuras completamente espeluznantes como el infierno de las devoluciones de llamadas. Sin embargo, no puedes prescindir de él.

Hoy proponemos hablar sobre las características de devoluciones de llamada, promesas, generadores y construcciones asíncronas/await, y pensar en cómo escribir código asincrónico simple, comprensible y efectivo.

Acerca del código sincrónico y asincrónico Comencemos mirando fragmentos de código JS sincrónico y asincrónico. Aquí, por ejemplo, se muestra un código síncrono típico:

Consola.log("1") consola.log("2") consola.log("3")
Éste, sin mucha dificultad, muestra los números del 1 al 3 en la consola.

Ahora el código es asincrónico:

Console.log("1") setTimeout(función afterTwoSeconds() ( console.log("2") ), 2000) console.log("3")
Aquí se generará la secuencia 1, 3, 2. El número 2 se genera desde la devolución de llamada, que procesa el evento del temporizador especificado al llamar a la función setTimeout. La devolución de llamada se realizará, en este ejemplo, después de 2 segundos. La aplicación no dejará de esperar a que expiren estos dos segundos. En cambio, continuará ejecutándose y llamará a la función afterTwoSeconds cuando expire el temporizador.

Quizás, si estás empezando como desarrollador JS, te preguntarás: “¿Para qué sirve todo esto? ¿Quizás sea posible convertir código asincrónico en sincrónico? Busquemos respuestas a estas preguntas.

Planteamiento del problema Supongamos que nos enfrentamos a la tarea de encontrar un usuario de GitHub y descargar datos sobre sus repositorios. El principal problema aquí es que no sabemos el nombre de usuario exacto, por lo que debemos enumerar todos los usuarios con nombres similares al que estamos buscando y sus repositorios.

En cuanto a la interfaz, nos limitaremos a algo sencillo.


Interfaz sencilla para buscar usuarios de GitHub y sus correspondientes repositorios

En los ejemplos, la ejecución de la solicitud se realizará mediante XMLHttpRequest (XHR), pero aquí puede utilizar fácilmente jQuery ($.ajax), o un enfoque estándar más moderno basado en el uso de la función de recuperación. Ambos se reducen al uso de promesas. El código cambiará según el viaje, pero aquí tienes un ejemplo para empezar:

// el argumento de la URL podría ser algo así como "https://api.github.com/users/daspinola/repos" solicitud de función (url) ( const xhr = new XMLHttpRequest(); xhr.timeout = 2000; xhr.onreadystatechange = function(e) ( if (xhr.readyState === 4) ( if (xhr.status === 200) ( // Código para procesar la finalización exitosa de la solicitud ) else ( // Procesar la respuesta con un mensaje de error ) ) ) xhr .ontimeout = function () ( // Esperar una respuesta tomó demasiado tiempo, aquí está el código que maneja una situación similar ) xhr.open("get", url, true) xhr.send();
Tenga en cuenta que en estos ejemplos lo importante no es lo que finalmente vendrá del servidor y cómo se procesará, sino la organización del código en sí utilizando diferentes enfoques que puede utilizar en su desarrollo asincrónico.

Funciones de devolución de llamada Puedes hacer muchas cosas con funciones en JS, incluso pasarlas como argumentos a otras funciones. Esto generalmente se hace para llamar a la función pasada después de que se haya completado algún proceso, lo que puede llevar algún tiempo. Estamos hablando de funciones de devolución de llamada. He aquí un ejemplo sencillo:

// Llama a la función "doThis" con otra función como parámetro, en este caso la función "andThenThis". La función "doThis" ejecutará el código contenido en ella, tras lo cual, en el momento adecuado, llamará a la función "andThenThis". doThis(andThenThis) // Dentro de "doThis", se accede a la función que se le pasa a través del parámetro "callback", de hecho, es solo una variable que almacena una referencia a la función función andThenThis() ( console.log(" y luego esto") ) // Puedes nombrar el parámetro en el que la función de devolución de llamada será lo que quieras, "devolución de llamada" es solo una función de opción común doThis(callback) ( console.log("this first") // En orden de la función a la que se almacena la referencia en la variable que se llamó, es necesario colocar un paréntesis después del nombre de la variable, “()”, de lo contrario nada funcionará callback())
Usando este enfoque para resolver nuestro problema, podemos escribir una función de solicitud como esta:

Solicitud de función (url, devolución de llamada) ( const xhr = new XMLHttpRequest(); xhr.timeout = 2000; xhr.onreadystatechange = función(e) ( if (xhr.readyState === 4) ( if (xhr.status === 200) ( callback(null, xhr.response) ) else ( callback(xhr.status, null) ) ) xhr.ontimeout = function () ( console.log("Timeout") ) xhr.open("get", url , verdadero) xhr.send();
Ahora la función para ejecutar la solicitud toma el parámetro de devolución de llamada, por lo tanto, después de ejecutar la solicitud y recibir la respuesta del servidor, se llamará a la devolución de llamada tanto en caso de error como en caso de que la operación se complete con éxito.

Const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` request(userGet, función handleUsersList(error, usuarios) (si (error) arroja error const list = JSON.parse (usuarios).items list.forEach(function(user) ( request(user.repos_url, function handleReposList(err, repos) ( if (err) throw err //Procesaremos la lista de repositorios aquí)) )) ))
Veamos lo que está pasando aquí:

  • Se realiza una consulta para obtener los repositorios del usuario (en este caso estoy cargando mis propios repositorios);
  • Una vez completada la solicitud, se llama a la devolución de llamada handleUsersList;
  • Si no hubo errores, analizamos la respuesta del servidor usando J SON.parse y la convertimos, por conveniencia, en un objeto;
  • Después de eso, iteramos sobre la lista de usuarios, ya que puede tener más de un elemento, y para cada uno de ellos consultamos la lista de repositorios usando la URL devuelta para cada usuario después de la primera solicitud. La implicación es que repos_url es la URL de nuestras próximas solicitudes y la obtuvimos de la primera solicitud.
  • Cuando se completa la solicitud para cargar los datos del repositorio, se llama a la devolución de llamada, ahora handleReposList. Aquí, al igual que cuando se carga una lista de usuarios, puede manejar errores o cargas útiles que contienen una lista de repositorios de usuarios.
Tenga en cuenta que utilizar un objeto de error como primer parámetro es una práctica común, especialmente para el desarrollo de Node.js.

Si le damos a nuestro código una apariencia más completa, le proporcionamos herramientas de manejo de errores y separamos la definición de funciones de devolución de llamada del código de ejecución de la solicitud, lo que mejorará la legibilidad del programa, obtendremos lo siguiente:

Pruebe ( request(userGet, handleUsersList) ) catch (e) ( console.error("Solicitud boom!", e) ) function handleUsersList(error, usuarios) ( if (error) throw error const list = JSON.parse(users) .items list.forEach(function(user) ( request(user.repos_url, handleReposList) )) ) function handleReposList(err, repos) ( if (err) throw err // Procesa la lista de repositorios aquí console.log("Mi muy pocos repos", repos) )
Este enfoque funciona, pero al usarlo corremos el riesgo de encontrarnos con problemas como condiciones de carrera y errores de dificultad para manejar. Sin embargo, el principal problema con las devoluciones de llamada, de las cuales hay tres, contando lo que sucede en el bucle forEach, es que dicho código es difícil de leer y mantener. Este problema ha existido desde la llegada de las funciones de devolución de llamada y se conoce comúnmente como infierno de devolución de llamada.


Infierno de devolución de llamada en su máxima expresión. Imagen tomada desde aquí.

En este caso, por "condición de carrera" nos referimos a una situación en la que no controlamos el orden en el que se recuperan los datos sobre los repositorios de los usuarios. Consultamos datos de todos los usuarios y es posible que las respuestas a estas consultas sean mixtas. Digamos que la respuesta para el décimo usuario será la primera y la del segundo, la última. A continuación hablaremos de una posible solución a este problema.

Promesas Usando promesas puedes mejorar la legibilidad de tu código. De este modo, por ejemplo, si un nuevo desarrollador llega a su proyecto, comprenderá rápidamente cómo funciona todo allí.

Para crear una promesa, puede utilizar la siguiente construcción:

Const miPromesa = nueva Promesa(función(resolver, rechazar) ( // El código irá aquí si (códigoIsFine) ( resolver("bien") ) else ( rechazar("error") ) )) miPromise .then(función cuandoOk( respuesta) (consola.log(respuesta) devuelve respuesta)).catch(función notOk(err) (consola.error(err)))
Veamos este ejemplo:

  • La promesa se inicializa mediante una función que contiene llamadas a los métodos de resolución y rechazo;
  • El código asincrónico se coloca dentro de una función creada con el constructor Promise. Si el código se ejecuta correctamente, se llama al método de resolución; si no, se llama al rechazo;
  • Si la función llama a resolve, se ejecutará el método .then en el objeto Promise; de ​​manera similar, si se llama a rechazar, se ejecutará el método .catch.
Esto es lo que debe recordar al trabajar con promesas:
  • Los métodos de resolución y rechazo toman solo un parámetro, como resultado, por ejemplo, al ejecutar un comando como resolve("yey", "works"), solo se pasará "yey" a callback.then;
  • Si encadenas varias llamadas .then, siempre debes usar return al final de las devoluciones de llamada correspondientes; de lo contrario, todas se ejecutarán al mismo tiempo, lo que obviamente no es lo que deseas lograr;
  • Al ejecutar un comando de rechazo, si .then es el siguiente en la cadena, se ejecutará (puede considerar .then como una expresión que se ejecuta de todos modos);
  • En una cadena de llamadas .then, si alguna de ellas falla, las siguientes se omitirán hasta que se encuentre una expresión .catch;
  • Las promesas tienen tres estados: "pendiente": el estado de espera de una llamada de resolución o rechazo, así como los estados "resuelto" y "rechazado", que corresponden a exitosa, con una llamada a resolver, y sin éxito, con una llamada. rechazar, cumplimiento de la promesa. Una vez que una promesa está en estado "resuelta" o "rechazada", ya no se puede cambiar.
Tenga en cuenta que las promesas se pueden crear sin utilizar funciones definidas por separado, describiendo las funciones en el momento en que se crean las promesas. Lo que se muestra en nuestro ejemplo es solo una forma común de inicializar promesas.

Para no estancarnos en la teoría, volvamos a nuestro ejemplo. Reescribámoslo usando promesas.

Solicitud de función (url) ( devolver nueva Promesa (función (resolver, rechazar) ( const xhr = new XMLHttpRequest(); xhr.timeout = 2000; xhr.onreadystatechange = función(e) ( if (xhr.readyState === 4) ( if (xhr.status === 200) ( resolver(xhr.response) ) else ( rechazar(xhr.status) ) ) xhr.ontimeout = función () ( rechazar("timeout") ) xhr.open(" get ", URL, verdadero) xhr.send(); )) )
Con este enfoque, cuando llame a la solicitud, la devolución será algo como lo siguiente.

Esta es una promesa en estado pendiente. Puede resolverse con éxito o rechazarse.

Ahora, usando la nueva función de solicitud, reescribamos el resto del código.

Const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` const myPromise = request(userGet) console.log("estará pendiente cuando se inicie sesión", myPromise) myPromise .luego (función handleUsersList(usuarios) ( console.log("cuando se encuentra la resolución, viene aquí con la respuesta, en este caso usuarios ", usuarios) const list = JSON.parse(users).items return Promise.all(list.map (función (usuario) ( solicitud de devolución (usuario.repos_url) ))) )) .then (función handleReposList (repos) ( console.log ("Todos los usuarios repos en una matriz", repositorios) )) .catch (función handleErrors ( error) ( console.log("cuando se ejecuta un rechazo, vendrá aquí ignorando la declaración then ", error)))
Aquí es donde terminamos en la primera declaración .then cuando la promesa se resuelve con éxito. Tenemos una lista de usuarios. En la segunda expresión, luego pasamos una matriz con repositorios. Si algo sale mal, terminamos en una declaración .catch.

Utilizando este enfoque, entendimos la condición de carrera y algunos de los problemas que causaba. Aquí no hay un infierno de devolución de llamada, pero el código aún no es tan fácil de leer. De hecho, nuestro ejemplo se puede mejorar aún más eliminando las declaraciones de la función de devolución de llamada:

Const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` const userRequest = request(userGet) // Si acaba de leer esta parte del programa en voz alta, podrá hacerlo inmediatamente entienda qué hace exactamente el código userRequest .then(handleUsersList) .then(repoRequest) .then(handleReposList) .catch(handleErrors) function handleUsersList(users) ( return JSON.parse(users).items ) function repoRequest(users) ( return Promise.all(users. map(function(user) ( return request(user.repos_url) ))) ) function handleReposList(repos) ( console.log("Todos los usuarios repos en una matriz", repos) ) function handleErrors(error ) ( console.error( "Algo salió mal", error) )
Con este enfoque, una mirada a los nombres de las devoluciones de llamada en las expresiones .then revela el significado de la llamada userRequest. Es fácil trabajar con el código y fácil de leer.

De hecho, esto es sólo la punta del iceberg de lo que se llaman promesas. Aquí está el material que recomiendo leer para aquellos que quieran profundizar más en este tema.

Generadores Otro enfoque para resolver nuestro problema, que, sin embargo, rara vez se ve, son los generadores. Este tema es un poco más complejo que los demás, por lo que si cree que es demasiado pronto para estudiarlo, puede pasar inmediatamente a la siguiente sección de este material.

Para definir una función de generador, puede utilizar el asterisco “*” después de la palabra clave de función. Usando generadores, el código asincrónico se puede hacer muy similar al código sincrónico. Por ejemplo, podría verse así:

Función* foo() ( rendimiento 1 const args = rendimiento 2 console.log(args) ) var fooIterator = foo() console.log(fooIterator.next().value) // generará 1 console.log(fooIterator.next ( ).value) // generará 2 fooIterator.next("aParam") // resultará en llamar a console.log dentro del generador y generar "aParam"
El punto aquí es que los generadores, en lugar de return, usan una expresión de rendimiento, que detiene la ejecución de la función hasta la siguiente llamada al iterador .next. Esto es similar a la declaración .then en las promesas, que se ejecuta cuando se resuelve la promesa.

Veamos ahora cómo aplicar todo esto a nuestro problema. Así que aquí está la función de solicitud:

Solicitud de función (url) ( función de retorno (devolución de llamada) ( const xhr = new XMLHttpRequest(); xhr.onreadystatechange = función(e) ( if (xhr.readyState === 4) ( if (xhr.status === 200) ( callback(null, xhr.response) ) else ( callback(xhr.status, null) ) ) ) xhr.ontimeout = function () ( console.log("timeout") ) xhr.open("get", url, verdadero) xhr.send() ) )
Aquí, como siempre, usamos el argumento url, pero en lugar de ejecutar la solicitud inmediatamente, queremos ejecutarla solo cuando tengamos una función de devolución de llamada para procesar la respuesta.

El generador se verá así:

Función* lista() ( const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` const usuarios = solicitud de rendimiento (userGet) rendimiento para (let i = 0; i console .log("después de 2 segundos", resultado)) función asíncrona sumaTwentyAfterTwoSeconds(valor) ( ​​const resto = afterTwoSeconds(20) valor de retorno + espera resto) función afterTwoSeconds(valor) ( devuelve nueva promesa(resolver => ( setTimeout(() => ( resolver(valor) ), 2000 ));
Esto es lo que sucede:

  • Hay una función asincrónica sumTwentyAfterTwoSeconds;
  • Sugerimos que el código espere a que se resuelva la promesa afterTwoSeconds, lo que podría finalizar con una llamada de resolución o rechazo;
  • La ejecución del código finaliza en .then, donde se completa la operación de espera, en este caso solo una operación.
Preparemos la función de solicitud para usarla en la construcción async/await:

Solicitud de función (url) ( devolver nueva Promesa (función (resolver, rechazar) ( const xhr = new XMLHttpRequest(); xhr.onreadystatechange = función(e) ( if (xhr.readyState === 4) ( if (xhr.status === 200) ( resolver(xhr.response) ) else ( rechazar(xhr.status) ) ) xhr.ontimeout = función () ( rechazar("timeout") ) xhr.open("get", url, true ) xhr.enviar() )) )
Ahora creamos una función con la palabra clave async, en la que usamos la palabra clave await:

Lista de funciones asíncronas() ( const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` const usuarios = await request(userGet) const usersList = JSON.parse(usuarios). elementos userList.forEach(función asíncrona (usuario) ( const repos = await request(user.repos_url) handleRepoList(user, repos) )) function handleRepoList(user, repos) ( const userRepos = JSON.parse(repos) // Procesar aquí son repositorios para cada usuario console.log(user, userRepos) )
Entonces, tenemos una función de lista asincrónica que procesará la solicitud. También necesitaremos la construcción async/await en el bucle forEach para generar una lista de repositorios. Llamar a todo esto es muy sencillo:

Lista() .catch(e => consola.error(e))
Este enfoque y el uso de promesas son mis técnicas de programación asincrónica favoritas. El código escrito con ellos es fácil de leer y editar. Puede leer más sobre async/await.

La desventaja de async/await, así como la desventaja de los generadores, es que este diseño no es compatible con navegadores más antiguos y para usarlo en el desarrollo de servidores es necesario utilizar el Nodo 8. En tal situación, nuevamente, un transpilador, por ejemplo, babel, ayudará.

Los resultados se pueden ver en el código del proyecto, que resuelve el problema planteado al principio del material usando async/await. Si quieres entender adecuadamente de qué hablamos, experimenta con este código y con todas las tecnologías comentadas.

Tenga en cuenta que nuestros ejemplos podrían mejorarse y volverse más concisos reescribiéndolos utilizando métodos de consulta alternativos, como $.ajax y fetch. Si tiene ideas sobre cómo mejorar la calidad del código utilizando las técnicas descritas anteriormente, le agradecería que me lo contara.

Dependiendo de los detalles de la tarea que se le asigne, puede resultar que utilice async/await, devoluciones de llamada o alguna combinación de diferentes tecnologías. De hecho, la respuesta a la pregunta de qué metodología de desarrollo asincrónico elegir depende de las características específicas del proyecto. Si un enfoque resuelve un problema en un código que es legible, fácil de mantener y comprensible (y será comprensible con el tiempo) para usted y otros miembros del equipo, entonces ese enfoque es lo que necesita.

Queridos lectores! ¿Qué técnicas utilizas para escribir código asincrónico en JavaScript?

Etiquetas:

  • javascript
  • desarrollo
  • llamar de vuelta
  • asíncrono
  • esperar
  • promesa
  • generador
  • código asincrónico
Agregar etiquetas

La programación asincrónica tiene bastante demanda en la actualidad. Especialmente en el desarrollo web, donde la capacidad de respuesta de las aplicaciones juega un papel muy importante. Nadie quiere perder el tiempo esperando una aplicación congelada mientras ejecuta múltiples consultas a bases de datos, envía un correo electrónico o ejecuta otras tareas que pueden consumir mucho tiempo. Los usuarios quieren respuestas a sus acciones y quieren que esas respuestas sucedan instantáneamente. Cuando su aplicación se vuelva lenta, comenzará a perder clientes. Una vez que un usuario experimenta que una aplicación se congela, en la mayoría de los casos simplemente la cierra y nunca regresa. Cuando la interfaz se congela, desde el punto de vista del usuario no está claro si su aplicación no funciona o si está realizando algunas tareas largas y tarda algún tiempo en completarse.

Sensibilidad

Las aplicaciones modernas tienden a sensible, pero algunas tareas potencialmente largas u operaciones de bloqueo, como E/S de red y sistema de archivos o consultas de bases de datos, pueden ralentizar significativamente la aplicación. Para evitar que las aplicaciones bloqueando con estas operaciones podemos ejecutarlas en segundo plano, ocultando así los retrasos que crean. Entonces la aplicación permanece sensible, porque puede continuar funcionando, por ejemplo, puede devolver el control a la interfaz de usuario o responder a otros eventos.

Paralelismo vs asincronía

La mayoría de las personas cuando ven código asincrónico, inmediatamente piensan “¡Oh, esto es genial! ¡Puedo ejecutar mis tareas en paralelo!”. Puede que te decepcione, pero en realidad esto no es cierto: la concurrencia (asincronía) y el paralelismo no son lo mismo. Este es un error común, así que intentemos descubrir por qué.

Cuando algo se ejecuta de forma asincrónica, significa ejecución sin bloqueo sin esperar a que se complete. Mientras que el paralelismo significa ejecutar múltiples tareas separadas al mismo tiempo como partes independientes.

Asincronía:

Haz la tarea tú mismo en un tiempo libre y avísame cuando termines y tráeme los resultados. En este momento puedo continuar con mi tarea.

El código asincrónico requiere manejar dependencias entre secuencias de ejecución y lo hace mediante devoluciones de llamada. Cuando se completa una tarea, notifica a otra tarea que se ha completado. El código asincrónico se ocupa principalmente del tiempo (secuencia de eventos).

Paralelismo:

Contrata a tantas personas como quieras y divide la tarea entre ellos para completarla más rápido y avísame cuando hayas terminado. Puedo continuar con mis tareas o, si la tarea es urgente, me quedaré aquí esperando que regreses con los resultados. Entonces puedo combinar los resultados de estos muchachos. La ejecución paralela suele requerir más recursos, es decir, depende principalmente del hardware.

Para ilustrar la diferencia entre ejecución asíncrona y paralela con ejemplos de la vida real, podemos comparar dos servidores web populares: Apache y Nginx. Ilustran perfectamente esta diferencia: Nginx es asíncrono y está basado en eventos, mientras que Apache usa subprocesos paralelos. Apache crea nuevos subprocesos para cada conexión adicional, por lo que se permite una cantidad máxima de conexiones según la memoria disponible en el sistema. Cuando se alcanza este límite de conexiones, Apache rechaza conexiones adicionales. El factor limitante en la configuración de Apache es la memoria (recuerde que la ejecución paralela suele depender del hardware). Si el hilo se detiene, el cliente espera una respuesta hasta que el hilo se libera y devuelve una respuesta.

Nginx funciona de manera diferente a Apache y no crea nuevos hilos para cada solicitud entrante. Tiene un proceso de trabajo principal (o varios procesos; a menudo en la práctica se recomienda tener un trabajador por procesador (CPU)), que es de un solo subproceso. Este trabajador puede manejar miles de conexiones simultáneas. Lo hace de forma asincrónica con un solo subproceso, en lugar de utilizar la ejecución paralela de múltiples subprocesos.

Entonces, la asincronía (o concurrencia) es una forma de realizar tareas. Es una composición de tareas ejecutadas de forma independiente. La concurrencia es la ejecución simultánea de varias tareas (pueden estar relacionadas o no). En asincronía, nos ocupamos de muchas tareas diferentes al mismo tiempo. La concurrencia hace muchas cosas a la vez. Suena igual, pero las ideas subyacentes son diferentes. La concurrencia tiene que ver con la estructura, mientras que el paralelismo tiene que ver con la ejecución.

Del traductor:

Asincronía: Tiene una tarea de backend que está realizando actualmente. Un desarrollador front-end se ha acercado a usted para solicitarle que corrija ligeramente la API. Guardó en su rama, cambió a otra (actualmente está ocupado con una tarea API), la corrigió y regresó a su tarea. Luego te convocaron a una reunión sobre algún tema, tomaste un descanso, fuiste (en este momento estás realizando la tercera tarea, que continuará en la próxima reunión) y regresaste nuevamente a tu primera tarea. Realizó estas tres tareas de forma asincrónica; en otras palabras, interrumpió y cambió entre tareas, asignándoles un poco de tiempo.

Paralelismo: Si hablamos de paralelismo en la persona de una persona, me vienen a la mente un par de ejemplos obvios. Por ejemplo, tocar la guitarra y cantar. En este momento, una de tus manos está tocando los acordes, la otra tocando (o rasgueando) y además sigues cantando. En este punto, estás realizando tres tareas en paralelo. U otro ejemplo: escuchas a Mozart en la cena. Aquí realizas dos tareas en paralelo: comer y escuchar. Pero si volvemos a las tareas de desarrollo, obtenemos un ejemplo más claro. Imagina que estás trabajando en un equipo de 4 desarrolladores. Tu equipo es como una sola máquina con un procesador de cuatro núcleos. Cada desarrollador es un núcleo. Cuando comienza el sprint, cada uno de los 4 desarrolladores completa sus tareas en paralelo con los otros 3 desarrolladores, y al final del sprint lo juntas todo.

¿Por qué en el backend?

Ahora es posible que se sienta indignado porque realmente no le importa la capacidad de respuesta de la espalda. Tienes todas estas cosas desagradables como JavaScript asincrónico en el front-end, y todo lo que tu servidor tiene que hacer es simplemente responder a las solicitudes. Por lo tanto, garantizar la capacidad de respuesta del usuario es trabajo del frente, no suyo. Sí, esto es cierto, pero el backend no se limita solo a las respuestas API. A veces es necesario gestionar algunas tareas complejas, como un servidor para descargar vídeos. En este caso, quizás la respuesta no sea el factor clave, pero se nos ocurre falta de recursos porque la solicitud tiene que esperar. Puede esperar operaciones del sistema de archivos, conexiones de red, consultas de bases de datos, etc. A menudo estas operaciones de E/S son extremadamente lentas en comparación con los cálculos en la CPU, por ejemplo cuando convertimos archivos de vídeo. Y mientras guardamos o leemos lentamente un archivo, nuestro procesador tiene que esperar y no hacer nada, en lugar de realizar algún trabajo útil. Como decíamos, en lugar de esperar, podemos realizar estas tareas en segundo plano. ¿Cómo? Lea a continuación.

PHP asíncrono

El mundo JavaScript ya cuenta con soporte integrado y herramientas para escribir código asincrónico. Y también está NodeJs, que permite escribir aplicaciones asincrónicas. En JavaScript, podemos usar la función setTimeout() para demostrar código asincrónico:

SetTimeout(function() ( console.log("Después del tiempo de espera"); ), 1); console.log("Antes del tiempo de espera");

Cuando ejecutamos este código vemos lo siguiente:

Antes del tiempo de espera Después del tiempo de espera

La función setTimeout() pone en cola el código que se ejecutará después de que se haya completado la pila de llamadas actual. Esto significa que interrumpimos el flujo sincrónico de código y retrasamos la ejecución de alguna parte del código. La segunda llamada a console.log() se ejecutará antes que la primera (dentro de la función settimeout()), que estaba en cola.

¿Pero qué pasa con PHP? Bueno, en PHP listo para usar no tenemos herramientas buenas y convenientes para escribir código verdaderamente asincrónico. No existe una función equivalente a settimeout() y simplemente no podemos retrasar o poner en cola algún código. Es por eso que comenzaron a aparecer marcos y bibliotecas como Amp y ReactPHP. Su idea principal es ocultarnos las complejidades de bajo nivel del lenguaje y proporcionar herramientas y abstracciones de alto nivel que puedan usarse para escribir código asincrónico y administrar la concurrencia, tal como podríamos hacerlo en JavaScript y NodeJS.

¿Por qué debería usar PHP si tenemos NodeJs y Go?

Una pregunta similar surge con mayor frecuencia cuando se trata de PHP asíncrono. Por alguna razón, la comunidad a menudo contra usando PHP como herramienta para escribir código asincrónico. Siempre hay alguien que sugiere usar Go y NodeJs.

Hoy en día, todos los días aparecen nuevos lenguajes de programación: Go, Rust, CoffeeScript, lo que sea. Decidí que a mí también me gustaría crear mi propio lenguaje de programación, que al mundo le faltaba algún lenguaje nuevo...

Damas y caballeros, hoy les presento Schlecht!Script, un lenguaje de programación loco. Todos deberíamos empezar a usarlo ahora. Tiene todo lo que estamos acostumbrados: tiene declaraciones condicionales, hay bucles, hay funciones y funciones de orden superior. En general tiene todo lo que necesita un lenguaje de programación normal.

Lo que no es muy habitual, e incluso puede resultar desagradable a primera vista, es que en Schlecht!Script las funciones tienen colores.


Es decir, cuando declaras una función, cuando la llamas, especificas explícitamente su color.

Las funciones están disponibles en rojo y azul: dos colores.

Punto importante: dentro de las funciones azules solo puedes llamar a otras funciones azules. No se pueden llamar funciones rojas dentro de funciones azules.


Dentro de las funciones rojas puedes llamar tanto a funciones rojas como a funciones azules.


Decidí que tenía que ser así. Esto debería ser así en todos los idiomas.

Un punto sutil: ¡las funciones rojas son dolorosas de escribir y llamar! ¿A qué me refiero cuando digo "dolido"? El hecho es que ahora estoy estudiando alemán y decidí que todos deberíamos llamar a las funciones rojas solo en alemán; de lo contrario, el intérprete simplemente no entenderá lo que está tratando de introducir y simplemente no lo realizará.


Así es como deberías escribir funciones en alemán:


"!" necesario; después de todo, escribimos en alemán.

¿Cómo escribir en un idioma así? Tenemos dos caminos. Solo podemos usar funciones azules, que no son difíciles de escribir, pero internamente no podemos usar funciones rojas. Este enfoque no funcionará porque en un ataque de inspiración escribí la mitad de la biblioteca estándar en funciones rojas, lo siento mucho...

Una pregunta para usted: ¿utilizaría ese lenguaje? ¿Te he vendido Schlecht!Script?

Bueno, no tienes elección. Lo siento…

JavaScript es un gran lenguaje, a todos nos encanta, todos estamos aquí porque amamos JavaScript. Pero el problema es que JavaScript hereda algunas de las características de Schlecht!Script, y ciertamente no quiero alardear, pero creo que me robaron un par de ideas.

¿Qué heredan exactamente? JavaScript tiene funciones rojas y azules. Las funciones rojas en JavaScript son funciones asincrónicas, las funciones azules son funciones sincrónicas. Y todo se puede rastrear, la misma cadena... Las funciones rojas son difíciles de llamar en Schlecht!Script, y las funciones asincrónicas son dolorosas de llamar en JavaScript.

Y dentro de las funciones azules no podemos escribir funciones rojas. Diré más sobre esto más adelante.


¿Por qué duele? ¿De dónde viene el problema al llamar y escribir funciones asincrónicas?

Trabajamos de manera diferente con declaraciones condicionales, bucles y retornos. Try/catch no funciona para nosotros y las funciones asincrónicas rompen la abstracción.

Un poco más sobre cada punto.


Así es como se ve el código sincrónico, donde el proceso debería y el proceso son funciones sincrónicas, y las declaraciones condicionales funcionan, para las obras, en general, todo está bien.

Lo mismo, pero asincrónico, se verá así:

Allí apareció la recursividad, pasamos el estado a los parámetros, a la función. En general, es francamente desagradable de ver. Try/catch no funciona para nosotros, y creo que todos sabemos que si encapsulamos un bloque de código sincrónico en try/catch, no detectaremos la excepción. Necesitaremos pasar una devolución de llamada, volver a conectar el controlador de eventos, en general, no tenemos try/catch...

Y las funciones asincrónicas rompen la abstracción. ¿Qué quiero decir? Imagina que escribiste un caché. Ha creado un caché de usuario en la memoria. Y tienes una función que lee desde este caché, que es naturalmente síncrona porque todo está en la memoria. Mañana tendrás miles, millones, miles de millones de usuarios y necesitarás colocar este caché en Redis. Si coloca un caché en Redis, la función se vuelve asincrónica, porque debido a Redis, solo podemos leer de forma asincrónica y, en consecuencia, toda la pila que llamó a su función sincrónica tendrá que reescribirse, porque ahora toda la pila se vuelve asincrónica. any Esta función dependía de la función de lectura del caché; ahora también será asincrónica.

En general, hablando de asincronía en JavaScript, podemos decir que allí todo es triste.

Pero, ¿a qué nos referimos con la asincronía? Hablemos un poco de mí, por fin.


Vine a salvarlos a todos. Bueno, intentaré hacerlo.

Mi nombre es Andrey, trabajo en una startup llamada Productive Mobile en Berlín. Ayudo con la organización de MoscowJS y soy copresentador de RadioJS. Me interesa mucho el tema de la asincronía y no solo JavaScript, creo que, en principio, este es el momento decisivo del lenguaje. La forma en que un lenguaje maneja la asincronía determina su éxito y cuán agradable y cómoda se siente la gente trabajando con él.

Cuando hablamos de asincronía específicamente en JavaScript, creo que tenemos dos scripts con los que interactuamos constantemente. Este es el procesamiento de múltiples eventos y el procesamiento de operaciones asincrónicas únicas.

Un conjunto de eventos es algo así como un evento DOM o una conexión de servidor, algo que emite muchos tipos de eventos múltiples.

Una única operación es, por ejemplo, leer de una base de datos. Una única operación asincrónica devuelve un resultado o un error. No hay más opciones.

Y hablando de estos dos escenarios, es interesante especular: la asincronía es mala, en general todo es triste... Pero, ¿qué es lo que realmente queremos? ¿Cómo sería el código asincrónico ideal?


Y me parece que queremos controlar el flujo de control. Queremos que nuestras sentencias condicionales y bucles funcionen en código sincrónico de la misma manera que en código asincrónico.

Queremos manejo de excepciones. ¿Por qué necesitamos try/catch si no podemos usarlo en operaciones asincrónicas? Es simplemente extraño.

Y es deseable, por supuesto, tener una única interfaz. ¿Por qué una función asincrónica debería escribirse y llamarse de manera diferente a una sincrónica? Esto no debería suceder.

Eso es lo que queremos.

¿Qué tenemos hoy y qué herramientas tendremos en el futuro?


Si hablamos de ECMAScript 6 (esto es, en principio, de lo que hablaré hoy), para trabajar con múltiples eventos tenemos EventEmitter y Stream, y para trabajar con operaciones asíncronas individuales - Continuation Passing Style (también conocido como callback" i) , Promesas y Corrutinas.


En ECMAScript 7, tendremos generadores asíncronos para trabajar con múltiples eventos y Async/Await para trabajar con operaciones asíncronas individuales.

Hablemos de esto.

Comencemos con lo que tenemos en ECMAScript 6 para manejar muchos eventos asincrónicos. Permítame recordarle, por ejemplo, que se trata de procesar eventos del mouse o pulsaciones de teclas. Tenemos un patrón EventEmitter que se implementa en el navegador en Node.js. Se encuentra en casi cualquier API donde trabajemos con muchos eventos. EventEmitter nos dice que podemos crear un objeto que emite eventos y adjuntar controladores a cada tipo de evento.


La interfaz es muy sencilla. Podemos agregar un EventListener, eliminar un EventListener por el nombre del evento y pasar una devolución de llamada allí.


Por ejemplo, en XMLHttpRequest, cuando hablo de múltiples eventos, me refiero a que podemos tener múltiples eventos de progreso. Aquellos. A medida que cargamos algunos datos usando una solicitud AJAX, los eventos de progreso se activan y los eventos de carga, aborto y error se activan una vez cada uno:


El error es un evento especial, un evento universal en EventEmitter y Stream para notificar al usuario de un error.

Hay muchas implementaciones:


Aquí solo se enumeran algunas y al final del informe habrá un enlace donde todas estas implementaciones están disponibles.

Es importante decir que EventEmitter está integrado de forma predeterminada en Node.js.

Esto es algo que tenemos casi como estándar en la API y los navegadores en Node.js.

¿Qué más tenemos para trabajar con múltiples eventos? Arroyo.

Stream es un flujo de datos. ¿Qué son los datos? Pueden ser datos binarios, como datos de archivos, datos de texto u objetos o eventos. Los ejemplos más populares:


Hay varios tipos de corrientes:


Aquí estamos viendo una cadena de conversiones de archivos Stylus a archivos CSS, agregando un prefijo automático, porque todos amamos a Andrey Sitnik y su prefijo automático.

Puede ver que tenemos varios tipos de secuencias: la secuencia de origen es gulp.src, que lee archivos y emite objetos de archivo, que luego transforman las secuencias. El primer hilo de transformación crea el archivo CSS desde el lápiz óptico, el segundo hilo de transformación agrega prefijos. Y el último tipo de subproceso es un subproceso consumidor, que recibe estos objetos, escribe algo en algún lugar del disco y no emite nada.


Aquellos. Tenemos 3 tipos de flujos: fuente de datos, transformación y consumidor. Y estos patrones se pueden ver en todas partes, no solo al tragar, sino también al observar eventos DOM. Tenemos subprocesos que emiten eventos DOM, que los transforman, y algo que consume estos eventos DOM y devuelve un resultado específico.

Esto es lo que se puede llamar una cinta transportadora. Con la ayuda de los flujos, podemos construir tales cadenas cuando un objeto se coloca en algún lugar al comienzo del transportador, pasa por una cadena de transformaciones y, cuando las personas se acercan a él, cambian algo allí, agregan, eliminan y al final terminamos con algún automóvil.

Existen varias implementaciones de streams, o también Observables:


Los flujos integrados en Node.js son Node Streams.

Entonces tenemos un EventEmitter y un Stream. EventEmitter también está presente de forma predeterminada en todas las API. Stream es un complemento que podemos usar para unificar la interfaz para procesar múltiples eventos.


Cuando hablamos de los criterios por los cuales comparamos API asincrónicas, en general, las declaraciones de retorno y las declaraciones de bucle no nos funcionan, try/catch, por supuesto, no nos funciona, y todavía estamos lejos de una interfaz unificada; con operaciones sincrónicas.

En general, ECMAScript 6 no es muy bueno para trabajar con múltiples eventos.

Cuando hablamos de operaciones asincrónicas únicas, tenemos 3 enfoques en ECMAScript 6:


Estilo de paso de continuación, también conocido como devolución de llamada.


Creo que ya estáis todos acostumbrados a esto. Esto es cuando hacemos una solicitud asincrónica, pasamos una devolución de llamada allí y la devolución de llamada se llamará con un error o con un resultado. Este es un enfoque común y también está disponible en el navegador de Node.


Creo que todos ustedes también comprenden los problemas de este enfoque.


Así es como obtendríamos una fuente de tweets de usuarios de forma asincrónica si todas las funciones fueran sincrónicas.


El mismo código, pero de forma sincrónica, tiene este aspecto:


Puede ampliar la fuente para que sea más fácil de ver. Y acercarnos un poco más... Y entendemos perfectamente que esto es Schlecht!Script.


Dije que me robaron la idea.

Continuation Passing Style es una API estándar en el navegador en Node.js, trabajamos con ellos todo el tiempo, pero es un inconveniente. Por eso tenemos Promesas. Este es un objeto que representa una operación asincrónica.


La promesa es como una promesa de hacer algo en el futuro, de forma asincrónica.


Podemos adjuntar una "devolución de llamada" a una operación asincrónica usando el método then, y este es, en principio, el método principal en la API. Y podemos, muy importante, encadenar Promesas, podemos llamarlas secuencialmente, y cada función que es pasado a then también puede devolver Promises. Así es como se ve la solicitud de feed de Twitter de un usuario para Promises.

Si comparamos este enfoque con el estilo de paso de continuación, las promesas son naturalmente más convenientes de usar: nos dan la oportunidad de escribir mucho menos texto repetitivo.

El estilo de paso de continuación todavía se usa de forma predeterminada en todas las API, Node.js, io.js, y ni siquiera planean cambiar a Promises por varias razones. Al principio, muchos dijeron que los motivos eran el rendimiento. Y esto es cierto, la investigación de 2013 muestra que las Promesas están muy por detrás de las devoluciones de llamada. Pero con la llegada de bibliotecas como bluebird, ya podemos decir con seguridad que este no es el caso, porque las Promesas en bluebird se acercan a las devoluciones de llamada en rendimiento. Un punto importante: ¿por qué no se recomienda el uso de Promises en API hasta ahora? Porque cuando emites Promesas desde tu API, estás forzando la implementación.

Todas las Promesas de una biblioteca deben cumplir con el estándar, pero al emitir Promesas, también estás emitiendo una implementación, es decir. Si escribió su código usando Promesas lentas y emite Promesas lentas desde la API, no será muy agradable para los usuarios. Por lo tanto, para las API externas, por supuesto, todavía se recomienda utilizar la devolución de llamada".


Hay un montón de implementaciones de Promises, y si no has escrito tu propia implementación, no eres un verdadero programador de JavaScript. No escribí mi propia implementación de Promises, así que tuve que crear mi propio lenguaje.

Entonces, Promises es, en general, un poco menos que un texto estándar, pero aún así no es tan bueno.

¿Qué pasa con las corrutinas? Aquí es donde las cosas empiezan a ponerse interesantes. Imaginar…

Esto es interesante. Estábamos en JSConf en Budapest, y había un loco que estaba programando un quadcopter y algo más en JavaScript, y la mitad de lo que intentó mostrarnos, no pudo hacerlo. Por eso decía constantemente: "Bueno, ahora imagina... Este cuadricóptero despegó y todo salió bien...".

Imaginemos que podemos pausar una función en algún momento durante su ejecución.


Aquí la función obtiene el nombre del usuario, ingresa a la base de datos, obtiene el objeto del usuario y devuelve su nombre. Naturalmente, "ingrese a la base de datos": la función getUser es asincrónica. ¿Qué pasaría si pudiéramos pausar la función getUserName cuando se llama a getUser? Ahora, estamos ejecutando nuestra función getUserName, llegamos a getUser y nos detuvimos. getUser fue a la base de datos, recibió un objeto, lo devolvió a la función y continuamos la ejecución. ¿Qué tan genial sería eso?

El caso es que las Corrutinas nos dan esta oportunidad. Las corrutinas son una función que podemos pausar y reanudar en cualquier momento. Un punto importante: no paramos todo el programa.


Esta es una operación sin bloqueo. Detenemos la ejecución de una función específica en una ubicación específica.


¿Cómo se ve getUserName usando generadores de JavaScript? Necesitamos agregar "*" a la declaración de la función para indicar que la función devuelve un generador. Podemos usar la palabra clave "rendimiento" en el punto donde queremos pausar la función. Y es importante recordar que aquí getUser devuelve Promesas.

Porque Los generadores se inventaron originalmente para crear secuencias diferidas en JavaScript en general, usarlos para código sincrónico es un truco; Por lo tanto, necesitamos bibliotecas para compensar esto de alguna manera.


Aquí usamos "co" para envolver el generador y devolvernos una función asincrónica.

Entonces, esto es lo que obtenemos:


Tenemos una función dentro de la cual podemos usar if, for y otros operadores.

Para devolver un valor, simplemente escribimos return, como en una función síncrona. Podemos usar try/catch internamente y detectaremos la excepción.


Si las promesas con getUser se resuelven con un error, se generará como una excepción.

La función getUserName devuelve Promesas, por lo que podemos trabajar con ella de la misma manera que con cualquier Promesa, podemos adjuntar una devolución de llamada" y usar luego, cadena, etc.

Pero como dije, usar generadores para código asincrónico es un truco. Por lo tanto, no es recomendable utilizar la exposición como API externa. Pero está bien usarlo dentro de una aplicación, así que úselo si tiene la capacidad de transpilar su código.


Hay muchas implementaciones. Algunos usan generadores, que ya son parte del estándar, hay Fibers, que funcionan en Node.js y que no usan generador, pero tienen sus propios problemas.

En general, este es el tercer enfoque para trabajar con operaciones asincrónicas únicas, y sigue siendo un truco, pero ya podemos usar código casi sincrónico. Podemos usar sentencias condicionales, bucles y bloques try/catch.


Aquellos. ECMAScript 6 para trabajar con operaciones asincrónicas únicas parece acercarnos un poco más al resultado deseado, pero el problema de una interfaz única aún no está resuelto, ni siquiera en Coroutines, porque necesitamos escribir un “*” especial y usar el “ operador clave "rendimiento".


Entonces, en ECMAScript 6, tenemos EventEmitter y Stream para trabajar con múltiples eventos y para trabajar con operaciones asincrónicas únicas: CPS, Promises, Coroutines. Y todo esto parece genial, pero falta algo. Quiero más, algo valiente, atrevido, nuevo, quiero una revolución.

Y los chicos que escriben ES7 decidieron darnos una revolución y nos trajeron Async/Await y Async Generators.


Async/Await es un estándar que nos permite trabajar con operaciones asincrónicas únicas, como consultas de bases de datos.

Así es como escribimos getUserName en los generadores:


Y así es como se ve el mismo código usando Async/Await:


Todo es muy similar, en general, este es un paso de un truco a un estándar. Aquí tenemos la palabra clave "async", que indica que la función es asíncrona y devolverá una Promesa. Dentro de una función asincrónica, podemos usar la palabra clave "await" donde devolvemos una Promesa. Y podemos esperar a que se cumpla esta Promesa, podemos pausar la función y esperar a que se cumpla esta Promesa.


Y así funcionan los operadores condicionales, los bucles y el try/catch, es decir, las funciones asíncronas están legalizadas en ES7. Ahora decimos explícitamente que si la función es asíncrona, agregue la palabra clave "async". Y esto, en principio, no es tan malo, pero nuevamente no tenemos una única interfaz.

¿Qué pasa con múltiples eventos? Aquí tenemos un estándar llamado Async Generators.

¿Qué es exactamente un conjunto? ¿Cómo trabajamos con conjuntos en JavaScript?


Trabajamos con múltiples eventos usando bucles, así que trabajemos con múltiples eventos usando bucles.


Dentro de una función asincrónica, podemos usar la construcción clave "for... on", que nos permite iterar sobre colecciones asincrónicas. ¿Cómo sería?

En este ejemplo, observar nos devuelve algo sobre lo que podemos iterar, es decir Cada vez que el usuario mueva el mouse, tendremos un evento "mousemove". Nos encontramos en este ciclo y de alguna manera procesamos este evento. En este caso, dibujamos líneas en la pantalla.


Porque La función es asíncrona, es importante entender que devuelve una Promesa. Pero, ¿qué pasa si queremos devolver muchos valores, si queremos, por ejemplo, procesar de alguna manera los mensajes desde un socket web, filtrarlos? Aquellos. Tenemos una multitud que entra y una multitud que sale. Los generadores asíncronos nos ayudan aquí. Escribimos “función asíncrona *” y decimos que la función es asíncrona y devolvemos un conjunto de algún tipo.


En este caso estamos viendo el evento Message en el websocket y cada vez que ocurre hacemos algún tipo de verificación y si la verificación pasa estamos en la colección devuelta. ¿Cómo agregaríamos este Mensaje?


Además, todo esto sucede de forma asincrónica. Los mensajes no se acumulan, se devuelven a medida que llegan. Y todas nuestras sentencias condicionales, bucles y try/catch funcionan aquí también.

Pregunta: ¿Qué devuelve filterWSMessages?


Definitivamente esto no es una Promesa, porque es algún tipo de colección, algo así... Pero tampoco es una matriz.


Aún más. ¿Qué devuelven estas Observaciones que generan eventos?

Y devuelven el llamado. Objetos observables. Esta es una palabra nueva, pero en general, los Observables son flujos, estos son flujos. Así, el círculo se cierra.

En total, tenemos Async/Await para trabajar con operaciones únicas asíncronas y Async Generators para trabajar con varias.

Demos un paseo y hagamos una pequeña retrospectiva de dónde dejamos y a dónde llegamos.

Para obtener un tweet, en CPS escribiríamos el siguiente código:


Mucha repetición, manejo de errores, prácticamente manual y, en general, poco agradable.

Con Promise, el código se ve así:


El texto estándar es más pequeño, podemos manejar excepciones en un solo lugar, lo cual ya es bueno, pero, sin embargo, existen estas entonces..., ni try/catch ni los operadores condicionales funcionan.

Usando Async/Await obtenemos la siguiente construcción:


Y las corrutinas nos dan más o menos lo mismo.

Todo aquí es genial, excepto que debemos declarar esta función como "async".

Para eventos múltiples, si hablamos de eventos DOM, así es como manejaríamos el movimiento del mouse y dibujaríamos en la pantalla usando EventEmitter:


El mismo código, pero usando Streams y la biblioteca Kefir, se ve así:


Creamos un flujo de eventos de movimiento del mouse en la ventana, los filtramos de alguna manera y para cada valor llamamos a una función de devolución de llamada. Y, cuando llamamos a finalizar en esta transmisión, automáticamente nos cancelaremos la suscripción a los eventos en el DOM, lo cual es importante.

Los generadores asíncronos se ven así:


Esto es solo un bucle, iteramos a través de una colección de eventos asincrónicos y realizamos algunas operaciones en ellos.

En mi opinión, este es un camino enorme.

En conclusión, me gustaría decir algunas palabras sobre cómo, de hecho, dejar de depurar código asincrónico y empezar a vivir.

  • Defina su tarea, es decir Si trabaja con muchos eventos, tiene sentido mirar Streams o incluso Async Generators si tiene un transpilador.
  • Si trabaja con la base de datos, por ejemplo, envía solicitudes allí o envía solicitudes AJAX que pueden fallar o completarse, use Promesas.
  • Considere sus limitaciones. Si puede utilizar un transpilador, tiene sentido mirar Async/Await y Async Generators. Si está escribiendo una API, puede que no tenga sentido exponer Promise como una API externa y hacer todo en devoluciones de llamada.
  • Utilice las mejores prácticas, recuerde los eventos de error en las transmisiones y en EventEmitter.
  • Recuerde métodos especiales como Promise.all, etc.

Sé que todos están interesados ​​en el destino de Schlecht!Script, cuándo se publicará en GitHub, etc., pero el hecho es que debido a las constantes críticas, acusaciones de plagio, dicen que ese lenguaje ya existe, nada nuevo. No inventé, decidí cerrar el proyecto y dedicarme, tal vez, a algo útil, importante, interesante, tal vez incluso escriba mi propia biblioteca de Promesas.

Códigos (asincrónicos y síncronos): generación e instalación

Muy a menudo nuestros clientes tienen preguntas sobre cómo afectará la presencia de un mostrador incorporado al funcionamiento del sitio. Los scripts pueden aumentar el tiempo de carga de la página.

Anteriormente, Google Analytics ofrecía un código estándar que afectaba la velocidad de carga de la página y la recopilación de datos.

Actualmente, Google Analytics ofrece una versión asincrónica del código, que tiene una serie de ventajas sobre el código estándar.

código asincrónico es un fragmento de JavaScript mejorado que carga el código de seguimiento ga.js en segundo plano, sin pausar la carga de otros scripts y contenido en las páginas de su sitio web.

Beneficios de usar código asincrónico:

  • Recopilación más precisa de datos de visitas cortas
  • Si los clics se produjeron antes de que se cargara el código, los datos se guardarán
  • Si tiene instalada la versión estándar del código, se recomienda actualizar el código a asíncrono. Para hacer esto, debe eliminar el fragmento de código antiguo e incrustar código asincrónico en su lugar. Después de instalar el código asincrónico, los datos comenzarán a aparecer en su cuenta dentro de las 24 horas.

    • no utilice 2 códigos al mismo tiempo, esto puede generar datos inexactos;
    • el código asincrónico se encuentra en la parte inferior de la sección, a diferencia del código estándar, que se encuentra inmediatamente antes de la etiqueta de cierre.

    Generando e instalando código

    1. El código se crea a nivel de perfil. Debes ir a la pestaña "Administrador".

    3. El código resultante debe copiarse e incrustarse en la página antes de la etiqueta de cierre.

    *Una de las principales ventajas del código asincrónico es que se puede agregar en la parte superior del código HTML. Como resultado, es más probable que la información del flujo de clics se transmita antes de que el usuario abandone la página.

    En este artículo no consideramos modificaciones de código; se escribirá una publicación separada para esto.

    Uno de los puntos fuertes de JavaScript es su manejo de código asincrónico. En lugar de bloquear el hilo de una tarea, el código asincrónico pone en cola los eventos que se ejecutan después de que se hayan completado otras partes del programa. Sin embargo, para los principiantes, comprender el código asincrónico puede ser un proceso difícil. Esta lección tiene como objetivo aclarar la situación.

    Descripción básica del código asincrónico.

    Las funciones principales del código JavaScript asíncrono son setTimeout y setInterval. La función setTimeout ejecuta la función especificada después de que haya transcurrido un intervalo de tiempo específico. Toma una función de retorno como primer argumento y un tiempo (en milisegundos) como segundo argumento. A continuación se muestra un ejemplo de uso:

    Consola.log("a"); setTimeout(función() ( console.log("c") ), 500); setTimeout(función() ( console.log("d") ), 500); setTimeout(función() ( console.log("e") ), 500); consola.log("b");

    Esperamos ver "a", "b" en la consola y luego, después de unos 500 ms, "c", "d" y "e". Utilizo el término "acerca de" porque en realidad setTimeout funciona de forma impredecible. Incluso en la especificación HTML5 dice: "Por lo tanto, la API no garantiza que el temporizador se ejecutará exactamente de acuerdo con el cronograma especificado. Es probable que los retrasos se deban a la carga del procesador, otras tareas y otros factores".

    Curiosamente, el tiempo de espera no se producirá hasta que se haya ejecutado el resto del código del bloque. Es decir, si se establece un tiempo de espera y luego alguna función tarda mucho en ejecutarse, el tiempo de espera no comenzará a contar hasta que se complete la función. En realidad, las funciones asincrónicas setTimeout y setInterval están en cola, lo que se conoce como bucle de eventos.

    El bucle de eventos es una cola de funciones de retorno. Cuando se ejecuta una función asincrónica, la función de retorno se pone en cola. JavaScript no activa el procesamiento de bucle de eventos mientras se ejecuta el código que se ejecuta después de que se ejecuta la función asincrónica. Este hecho significa que el código JavaScript no es multiproceso, aunque parezca que lo es. El bucle de eventos es una cola FIFO (primero en entrar, primero en salir), lo que significa que las funciones de retorno se ejecutan en el orden en que se reciben. Se ha elegido JavaScript para la plataforma node.js Precisamente por el sencillo proceso de desarrollo de dicho código.

    AJAX

    JavaScript y XML asincrónicos (AJAX) han cambiado el perfil de JavaScript para siempre. El navegador puede actualizar la página web sin necesidad de reiniciar. El código para implementar AJAX en diferentes navegadores puede ser largo y tedioso. Pero gracias a jQuery(y otras bibliotecas), AJAX se ha convertido en una solución muy simple y elegante para proporcionar comunicaciones cliente-servidor.

    Recuperar datos de forma asincrónica utilizando el método jQuery $.ajax es un proceso simple entre navegadores que oculta el proceso real. Por ejemplo:

    Es común, pero incorrecto, suponer que los datos estarán disponibles inmediatamente después de llamar a $.ajax. Pero la realidad es otra:

    Xmlhttp.open("GET", "algunos/ur/1", verdadero); xmlhttp.onreadystatechange = función(datos) ( if (xmlhttp.readyState === 4) ( console.log(datos); ) ); xmlhttp.send(nulo);

    Se utiliza un método similar de publicación de eventos en el patrón mediador, que se utiliza en la biblioteca postal.js.. El patrón mediador tiene un mediador disponible para todos los objetos que captura y publica eventos. Con este enfoque, un objeto no tiene vínculos directos con otro objeto y, por lo tanto, todos los objetos están desacoplados entre sí.

    Nunca devuelva promesas a través de API públicas. Esta práctica obliga a los usuarios de API a cumplir promesas y dificulta la modernización del código. Pero una combinación de promesas para necesidades internas y eventos para API externas puede dar como resultado una excelente aplicación desacoplada y de fácil mantenimiento.

    En el ejemplo anterior, la función de devolución de llamada doSomethingCoolWithDirections se ejecuta cuando se completan las dos llamadas anteriores a las funciones de geocodificación. La función doSomethingCoolWithDirections puede tomar la respuesta recibida de getRoute y publicarla como un mensaje.

    Var doSomethingCoolWithDirections = función(ruta) ( postal.channel("ui").publish("direcciones.done", ( ruta: ruta )); );

    Este enfoque permite que otras áreas de la aplicación respondan a devoluciones de llamadas asincrónicas sin referencias directas al objeto que genera la solicitud. lo que hace posible actualizar múltiples áreas en una página cuando se recibe una referencia. En una configuración típica de jQuery Ajax, cambiar n=dirección requiere una llamada exitosa a la función de devolución de llamada. Este enfoque es difícil de mantener y el uso de mensajes hace que sea mucho más fácil actualizar múltiples áreas de la interfaz de usuario.

    Var UI = función() ( this.channel = postal.channel("ui"); this.channel.subscribe("directions.done", this.updateDirections).withContext(this); ); UI.prototype.updateDirections = function(data) ( // La ruta está disponible en data.route, ahora solo necesitas actualizar la interfaz); app.ui = nueva interfaz de usuario();

    Otras implementaciones del patrón de proxy se utilizan en las bibliotecas de amplificación., PubSubJS y radio.js.

    Conclusión

    JavaScript facilita la creación de aplicaciones asincrónicas. El uso de promesas, eventos o funciones con nombre evita el infierno de las devoluciones de llamadas.



    
    Arriba