YOU DON’T KNOW JS – 05: ASYNC & PERFOMANCE – APÈNDICE A: BIBLIOTECA AYNQUENCE en castellano

Aquí os presento el apéndice A del libro ASYNC & PERFOMANCE, el cual se centra en hacer una aproximación al uso de la biblioteca asíncrona asynquence creada por el propio autor. En él se hacen múltiples referencias a el libro por lo que es interesante leerlo antes de empezar a usar esta biblioteca, aunque tampoco es indispensable.

Apéndice A: Biblioteca asynquence


Los capítulos 1 y 2 entraron en detalles sobre los patrones típicos de programación asíncrona y cómo se resuelven comúnmente con los callbacks. Pero también vimos por qué los callbacks están fatalmente limitadas en capacidad, lo que nos llevó a los Capítulos 3 y 4, con Promesas y generadores que ofrecen una base mucho más sólida, confiable y razonable sobre la cual construir su asincronía.

He hecho referencia a mi propia biblioteca asynquence (http://github.com/getify/asynquence) — «async» + «sequence» = «asynquence» — varias veces en este libro, y ahora quiero explicar brevemente cómo funciona y por qué su diseño único es importante y útil.

En el siguiente apéndice, exploraremos algunos patrones de sincronización avanzados, pero probablemente querrá una biblioteca para que sean lo suficientemente agradables como para ser útiles. Usaremos asynquence para expresar esos patrones, así que querrá pasar un poco de tiempo aquí para conocer la biblioteca primero.

Asynquence no es obviamente la única opción para una buena codificación asíncrona; ciertamente hay muchas grandes bibliotecas en este espacio. Pero asynquence proporciona una perspectiva única al combinar lo mejor de todos estos patrones en una sola biblioteca, y además está construida sobre una sola abstracción básica: la secuencia (asíncrona).

Mi premisa es que los programas sofisticados de JS a menudo necesitan bits y piezas de diferentes patrones asíncronos entretejidos, y esto normalmente se deja a criterio de cada desarrollador. En lugar de tener que traer dos o más bibliotecas aíncronas diferentes que se centran en diferentes aspectos de la asincronía, asynquence las unifica en pasos de secuencia variados, con sólo una biblioteca central para aprender e implementar.

Creo que el valor es lo suficientemente fuerte con asynquence como para hacer que la programación de control de flujo asincrónico con semántica de estilo Promesa sea súper fácil de lograr, por lo que nos centraremos exclusivamente en esa librería aquí.

Para empezar, explicaré los principios de diseño detrás de asynquence, y luego ilustraremos cómo funciona su API con ejemplos de código.

Secuencias, diseño de abstracción


La comprensión de asynquence comienza con la comprensión de una abstracción fundamental: cualquier serie de pasos para una tarea, ya sean síncronos o asíncronos por separado, pueden ser considerados colectivamente como una «secuencia». En otras palabras, una secuencia es un contenedor que representa una tarea y se compone de pasos individuales (potencialmente asíncronos) para completarla.

Cada paso de la secuencia es controlado tras el telón por una Promesa (ver Capítulo 3). Es decir, cada paso que se añade a una secuencia crea implícitamente una Promesa que está conectada al extremo anterior de la secuencia. Debido a la semántica de Promises, cada paso del avance en una secuencia es asíncrono, incluso si se completa el paso de forma síncrona.

Además, una secuencia siempre procederá linealmente de paso a paso, lo que significa que el paso 2 siempre viene después de que finaliza el paso 1, y así sucesivamente.

Por supuesto, una nueva secuencia se puede forzar a partir de una secuencia existente, lo que significa que la horquilla sólo se produce una vez que la secuencia principal alcanza ese punto del flujo. Las secuencias también pueden combinarse de varias maneras, incluyendo tener una secuencia subsumida por otra secuencia en un punto particular del flujo.

Una secuencia es como una cadena de promesas. Sin embargo, con las cadenas Promise, no hay ningún «mango» para agarrar que haga referencia a toda la cadena. La Promesa a la que se hace referencia sólo representa el paso actual de la cadena más cualquier otro paso que cuelgue de ella. Esencialmente, usted no puede tener una referencia a una cadena de Promesa a menos que tenga una referencia a la primera Promesa en la cadena.

Hay muchos casos en los que resulta muy útil tener una manecilla que haga referencia a toda la secuencia colectivamente. El más importante de estos casos es con la secuencia abortar/cancelar. Como hemos tratado extensamente en el Capítulo 3, las promesas en sí mismas nunca deberían poder ser canceladas, ya que esto viola un imperativo fundamental del diseño: la inmutabilidad externa.

Pero las secuencias no tienen tal principio de diseño de inmutabilidad, sobre todo porque las secuencias no se pasan como contenedores de valor futuro que necesitan semántica de valor inmutable. Así que las secuencias son el nivel apropiado de abstracción para manejar el comportamiento de abortar/cancelar. las secuencias de asinquencia pueden ser abortadas en cualquier momento, y la secuencia se detendrá en ese punto y no irá por ninguna razón.

Hay muchas más razones para preferir una abstracción de secuencias en la parte superior de las cadenas Promise, para fines de control de flujo.

En primer lugar, el encadenamiento de Promesas es un proceso bastante manual, que puede resultar bastante tedioso una vez que se empieza a crear y encadenar Promesas en una amplia gama de programas, y este tedio puede actuar de forma contraproducente para disuadir al desarrollador de usar Promesas en lugares donde son muy apropiadas.

Las abstracciones están destinadas a reducir la repetición y el tedio, por lo que la abstracción de secuencias es una buena solución a este problema. Con Promesas, su enfoque está en el paso individual, y hay poca suposición de que usted mantendrá la cadena en marcha. En el caso de las secuencias, se adopta el enfoque opuesto, suponiendo que la secuencia seguirá teniendo más pasos añadidos indefinidamente.

Esta reducción de la complejidad de la abstracción es especialmente poderosa cuando empiezas a pensar en patrones de Promesa de orden superior (más allá de la race([...]) y all([...])).

Por ejemplo, en el medio de una secuencia, usted puede querer expresar un paso que es conceptualmente como un try...catch en el sentido de que el paso siempre resultará en éxito, ya sea la resolución principal de éxito prevista o una señal positiva de no error para el error detectado. O bien, es posible que desee expresar un paso que es como un bucle de reintento/hasta, en el que se sigue intentando el mismo paso una y otra vez hasta que se produce el éxito.

Este tipo de abstracciones no son triviales de expresar usando sólo primitivas de Promesa, y hacerlo en medio de una cadena de Promesa existente no es bonito. Pero si abstractas tu pensamiento en una secuencia, y consideras un paso como una envoltura alrededor de una Promesa, esa envoltura puede ocultar tales detalles, liberándote para que pienses en el control de flujo de la manera más sensata sin ser molestado por los detalles.

En segundo lugar, y quizás lo más importante, pensar en el control de flujo asíncrono en términos de pasos en una secuencia le permite abstraer los detalles de qué tipos de asincronía están involucrados con cada paso individual. Bajo las cubiertas, una Promesa siempre controlará el paso, pero por encima de las cubiertas, ese paso puede parecer una llamada de continuación (el simple valor predeterminado), o como una Promesa real, o como un generador de ejecución hasta la finalización, o …. Con suerte, lo entenderás.

Tercero, las secuencias se pueden torcer más fácilmente para adaptarse a los diferentes modos de pensar como la codificación basada en eventos, secuencias o reactiva. asynquence proporciona un patrón que yo llamo «secuencias reactivas» (que trataremos más adelante) como una variación de las ideas «reactivas observables» en RxJS («Extensiones Reactivas»), que permite que un evento repetible desencadene una nueva instancia de secuencia cada vez. Las promesas son de un solo disparo, por lo que es bastante incómodo expresar una asincronía repetitiva sólo con Promesas.

Otro modo alternativo de pensar invierte la capacidad de resolución/control en un patrón que yo llamo «secuencias iterables». En lugar de que cada paso individual controle internamente su propia finalización (y por lo tanto el avance de la secuencia), la secuencia se invierte de modo que el control de avance se realiza a través de un iterador externo, y cada paso de la secuencia iterable sólo responde al control del iterador del next(...).

Exploraremos todas estas diferentes variaciones a medida que avanzamos en el resto de este apéndice, así que no se preocupe si hemos pasado por encima de esos bits demasiado rápido en este momento.

La conclusión es que las secuencias son una abstracción más poderosa y sensible para la asincronía compleja que las Promesas (Cadenas de Promesas) o los generadores, y la asincuencia está diseñada para expresar esa abstracción con el nivel justo de azúcar para hacer que la programación asincrónica sea más comprensible y más agradable.

API asynquence


Para empezar, la forma de crear una secuencia (una instancia de asynquence) es con la función ASQ(...). Una llamada ASQ() sin parámetros crea una secuencia inicial vacía, mientras que pasar uno o más valores o funciones a ASQ(...) establece la secuencia con cada argumento que representa los pasos iniciales de la secuencia.

Nota: Para los propósitos de todos los ejemplos de código aquí, usaré el identificador de nivel superior de asynquence en el uso del navegador global: ASQ. Si incluye y utiliza asynquence a través de un sistema de módulos (navegador o servidor), por supuesto puede definir el símbolo que prefiera, ¡y a asynquence no le importará!

Muchos de los métodos de la API discutidos aquí están incorporados en el núcleo de asynquence, pero otros se proporcionan a través de la inclusión del paquete opcional de plug-ins «contrib». Consulte la documentación de asynquence para saber si un método está incorporado o definido a través de un plug-in: http://github.com/getify/asynquence

Pasos

Si una función representa un paso normal en la secuencia, se llama a esa función, siendo el primer parámetro la llamada de continuación, y cualquier parámetro posterior cualquier mensaje transmitido desde el paso anterior. El paso no se completará hasta que se llame a la llamada de continuación. Una vez que se llama, cualquier argumento que le pase será enviado como mensaje al siguiente paso de la secuencia.

Para añadir un paso normal adicional a la secuencia, llame a then(...) (que tiene esencialmente la misma semántica que la llamada ASQ(...)):

Nota: Aunque el nombre then(...) es idéntico al de la API nativa de Promises, este then(...) es diferente. Puede pasar tantas funciones o valores a then(...) como desee, y cada una se toma como un paso separado. No hay dos llamadas de retorno cumplidas/rechazadas involucradas.

A diferencia de las Promesas, donde para encadenar una Promesa a la siguiente tienes que crear y devolver esa Promesa desde un manejador de cumplimiento then(...), con asynquence, todo lo que necesitas hacer es llamar a la continuación del callback – yo siempre la llamo done() pero puedes nombrarla como te convenga – y opcionalmente pasarle mensajes de finalización como argumentos.

Cada paso definido por then(...) se asume que es asíncrono. Si tiene un paso que es síncrono, puede llamar a done(...) inmediatamente, o puede usar el asistente de pasos val(...) más sencillo:

Como puede ver, los pasos invocados por val(...) no reciben una llamada de continuación, ya que esa parte es asumida por usted – y la lista de parámetros está menos desordenada como resultado! Para enviar un mensaje al siguiente paso, simplemente utilice return.

Piense en val(...) como un paso síncrono de «sólo valor», que es útil para operaciones de valor síncrono, registro y similares.

Errores

Una diferencia importante con asynquence en comparación con Promesas es el manejo de errores.

Con Promesas, cada Promesa (paso) individual en una cadena puede tener su propio error independiente, y cada paso posterior tiene la capacidad de manejar el error o no. La razón principal de esta semántica viene (de nuevo) del enfoque en las Promesas individuales más que en la cadena (secuencia) como un todo.

Creo que la mayoría de las veces, un error en una parte de una secuencia generalmente no es recuperable, por lo que los pasos subsiguientes en la secuencia son discutibles y deben ser omitidos. Así que, por defecto, un error en cualquier paso de una secuencia pone a toda la secuencia en modo de error, y el resto de los pasos normales son ignorados.

Si necesita tener un paso en el que su error sea recuperable, hay varios métodos de API diferentes que pueden acomodarse, como try(...) — previamente mencionado como una especie de paso try...catch — o until(...) (hasta) — un bucle de reintento que sigue intentando el paso hasta que tiene éxito o usted rompe manualmente el bucle con break(). asynquence tiene incluso los métodos pThen(...) y pCatch(..), que funcionan de forma idéntica al funcionamiento normal de Promise then(...) y catch(...) (véase el Capítulo 3), por lo que puede realizar una gestión de errores localizada en la mitad de la secuencia si así lo desea.

El punto es que tienes ambas opciones, pero la más común en mi experiencia es la predeterminada. Con Promises, para obtener una cadena de pasos para ignorar todos los pasos una vez que ocurre un error, hay que tener cuidado de no registrar un manejador de rechazo en ningún paso; de lo contrario, ese error se traga como se maneja, y la secuencia puede continuar (tal vez de forma inesperada). Este tipo de comportamiento deseado es un poco incómodo de manejar de forma adecuada y fiable.

Para registrar un manejador de notificación de error de secuencia, asynquence proporciona un método de secuencia or(...), que también tiene un alias de onerror(...). Puede llamar a este método en cualquier parte de la secuencia, y puede registrar tantos manejadores como desee. Esto hace que sea fácil para múltiples consumidores diferentes escuchar en una secuencia para saber si falló o no; es algo así como un manejador de eventos de error en ese sentido.

Al igual que con Promises, todas las excepciones de JS se convierten en errores de secuencia, o puede señalar programáticamente un error de secuencia:

Otra diferencia realmente importante con el manejo de errores en asynquence en comparación con las Promesas nativas es el comportamiento por defecto de las «excepciones no manejadas». Como discutimos ampliamente en el Capítulo 3, una Promesa rechazada sin un manejador de rechazo registrado sólo retendrá silenciosamente (tosea se lo comerá) el error; tienes que recordar siempre terminar una cadena con una catch(...) final.

En asynquence, la suposición se invierte.

Si se produce un error en una secuencia, y en ese momento no hay ningún gestor de errores registrado, el error se notifica a la consola. En otras palabras, los rechazos desatendidos son siempre reportados por defecto para no ser tragados y pasados por alto.

Tan pronto como se registra un controlador de errores contra una secuencia, se opta por que esa secuencia no se incluya en dichos informes, para evitar la duplicación de ruido.

De hecho, puede haber casos en los que quiera crear una secuencia que pueda pasar al estado de error antes de que tenga la oportunidad de registrar el manejador. Esto no es común, pero puede suceder de vez en cuando.

En esos casos, también puede optar por que una instancia de secuencia no se incluya en el informe de errores llamando a defer() en la secuencia. Usted sólo debe optar por no reportar errores si está seguro de que eventualmente va a manejar tales errores:

Este es un mejor comportamiento de manejo de errores que el de las Promesas mismas, porque es el Foso del Éxito, no el Foso del Fracaso (ver Capítulo 3).

Nota: Si una secuencia es canalizada dentro (es decir, asumida por) otra secuencia — ver «Combinando Secuencias» para una descripción completa — entonces en la secuencia fuente se opta por no reportar errores, pero ahora se debe considerar el reporte de errores de la secuencia objetivo o la falta de éste.

Pasos paralelos

No todos los pasos de sus secuencias tendrán una sola tarea (asíncrona) que realizar; algunos necesitarán realizar múltiples pasos «en paralelo» (concurrentemente). Un paso en una secuencia en la que múltiples subpasos se procesan simultáneamente se llama gate(...) — hay un alias all(...) si lo prefiere — y es directamente simétrico al Promise.all([...]) nativo.

Si todos los pasos en gate(...) se completan con éxito, todos los mensajes de éxito se pasarán al siguiente paso de la secuencia. Si alguno de ellos genera errores, toda la secuencia pasa inmediatamente a un estado de error.

Considere:

A modo de ejemplo, comparemos ese ejemplo con las Promesas nativas:

Qué asco. Las promesas requieren mucha más sobrecarga repetitiva para expresar el mismo control de flujo asíncrono. Esta es una gran ilustración de por qué la API de asynquence y la abstracción hacen que el manejo de los pasos de Promise sea mucho más agradable. La mejora sólo es mayor cuanto más compleja sea tu asincronía.

Variaciones de pasos

Hay varias variaciones en el paquete contrib de los plug-ins en el tipo de paso gate(...) de asynquence que pueden ser muy útiles:

  • any(...) es como gate(...), excepto que sólo un segmento tiene que tener éxito para proceder en la secuencia principal.
  • first(...) es como any(...), excepto que tan pronto como cualquier segmento tiene éxito, la secuencia principal procede (ignorando los resultados subsecuentes de otros segmentos).
  • race(...) (simétrico con Promise.race([...])) es como first(...), excepto que la secuencia principal procede tan pronto como cualquier segmento se completa (éxito o fracaso).
  • last(...) es como any(...), excepto que sólo el último segmento a completar con éxito envía su(s) mensaje(s) a la secuencia principal.
  • none(...) es lo contrario de gate(...): la secuencia principal procede sólo si todos los segmentos fallan (con todos los mensajes de error de los segmentos transpuestos como mensajes de éxito y viceversa).

Primero definamos algunos ayudantes para hacer más limpia la ilustración:

Ahora, vamos a demostrar estas variaciones de pasos de gate(…):

Otra variación de paso es map(...), que le permite mapear asíncronamente elementos de un array a diferentes valores, y el paso no procede hasta que todos los mapeos estén completos. map(..) es muy similar a gate(..), excepto que obtiene los valores iniciales de un array en lugar de las funciones especificadas por separado, y también porque usted define una llamada de retorno de una sola función para operar sobre cada valor:

Además, map(...) puede recibir cualquiera de sus parámetros (el array o el callback) de los mensajes pasados desde el paso anterior:

Otra variación es waterfall(...), que es como una mezcla entre el comportamiento de recolección de mensajes de gate(...) y el procesamiento secuencial de then(...).

Primero se ejecuta el paso 1, luego el mensaje de éxito del paso 1 se da al paso 2, y luego ambos mensajes de éxito van al paso 3, y luego los tres mensajes de éxito van al paso 4, y así sucesivamente, de tal forma que los mensajes se recogen y caen en cascada por el «waterfall».

Considere:

Si en cualquier punto de la «cascada»(«waterfall») se produce un error, toda la secuencia pasa inmediatamente a un estado de error.

Tolerancia de error

A veces se desea gestionar los errores a nivel de paso y no permitir que envíen necesariamente toda la secuencia al estado de error. asynquence ofrece dos variaciones de paso para ese propósito.

try(...) intenta un paso, y si tiene éxito, la secuencia procede como de costumbre, pero si el paso falla, el fallo se convierte en un mensaje de éxito formateado como { catch: ..} con el/los mensaje(s) de error cumplimentado(s):

En su lugar, puede configurar un bucle de reintento utilizando until(...), que intenta el paso y, si falla, lo reintenta de nuevo en el siguiente bucle de evento, y así sucesivamente.

Este bucle de reintento puede continuar indefinidamente, pero si desea salir del bucle, puede llamar al indicador break() en el lanzador de finalización, que envía la secuencia principal a un estado de error:

Pasos de estilo Promesas

Si prefiere tener, en línea en su secuencia, semántica de estilo Promesa como then(..) y catch(..) de Promesas (ver Capítulo 3), puede usar los plug-ins pThen y `pCatch:

pThen(...) y pCatch(...) están diseñados para funcionar en la secuencia, pero se comportan como si fuera una cadena Promise normal. Como tal, puede resolver Promesas genuinas o secuencias asincrónicas del manejador «fulfillment» pasado a pThen(...) (ver Capítulo 3).

Bifurcación de secuencias

Una característica que puede ser muy útil en Promesas es que se pueden adjuntar múltiples registros de manejadores then(...) a la misma promesa, lo que efectivamente «bifurca» el control de flujo en esa promesa:

La misma «bifurcación» es fácil en asynquence con fork():

Combinación de secuencias

Al revés de fork()ing, puede combinar dos secuencias una asumiendo otra, usando el método de instancia seq(...):

seq(...) puede aceptar una secuencia en sí misma, como se muestra aquí, o una función. Si se trata de una función, se espera que la función al ser llamada devuelva una secuencia, por lo que el código anterior podría haberse hecho con ella:

Además, ese paso podría haberse realizado con un pipe(..):

Cuando una secuencia es asumida, tanto su flujo de mensajes de éxito como su flujo de errores son canalizados.

Nota: Como se mencionó en una nota anterior, la canalización (manualmente con pipe(...) o automáticamente con seq(...)) opta por no reportar errores en la secuencia fuente, pero no afecta el estado del reporte de errores de la secuencia objetivo.

Secuencias de valores y errores


Si cualquier paso de una secuencia es sólo un valor normal, ese valor se asigna al mensaje de finalización de ese paso:

Si quieres hacer una secuencia que es automáticamente errónea:

También es posible que desee crear automáticamente un valor retrasado o una secuencia de error retrasada. Usando los plug-ins de contribuciones after y failAfter, esto es fácil:

También puede insertar un retardo en medio de una secuencia usando after(...):

Promesas y Callbacks


Creo que las secuencias de asynquence proporcionan mucho valor sobre las Promesas nativas, y en su mayor parte te parecerá más agradable y poderoso trabajar a ese nivel de abstracción. Sin embargo, la integración de la asynquence con otros códigos de no asynquence será una realidad.

Puede absumir fácilmente una promesa (por ejemplo, thenable — ver Capítulo 3) en una secuencia usando el método de instancia promise(...):

Y para ir en la dirección opuesta y bifurcar una promesa de una secuencia en un determinado paso, utilice el plug-in de contrib toPromise:

Para adaptar asynquence a los sistemas que utilizan callbacks, existen varias facilidades de ayuda. Para generar automáticamente un callback «estilo error-primero» desde su secuencia para conectarla a una utilidad orientada al callback, utilice errfcb:

También puede querer crear una versión envuelta en secuencia de una utilidad — comparada con el «promisory» en el Capítulo 3 y «thunkory» en el Capítulo 4 — y asynquence proporciona ASQ.wrap(...) para ese propósito:

Nota: En aras de la claridad (¡y por diversión!), acuñemos otro término, para una función de producción de secuencias que viene de ASQ.wrap(...), como coolUtility aquí. Propongo «sequory» («sequence» + «factory») («secuencial») («secuencia» + «fábrica»).

Secuencias Iterables


El paradigma normal para una secuencia es que cada paso es responsable de completarse a sí mismo, que es lo que hace avanzar la secuencia. Las promesas funcionan de la misma manera.

La parte desafortunada es que a veces se necesita un control externo sobre una Promesa/paso, lo que conduce a una incómoda «extracción de capacidades».

Considere este ejemplo de Promesas:

El anti-patrón de «capability extraction» con Promises tiene el siguiente aspecto:

Nota: Este anti-patrón es un código que huele mal, en mi opinión, pero a algunos desarrolladores les gusta, por razones que no puedo entender.

asynquence ofrece un tipo de secuencia invertida que yo llamo «secuencias iterables», que externaliza la capacidad de control (es muy útil en casos de uso como el domready):

Hay más en las secuencias iterables de lo que vemos en este escenario. Volveremos a ellos en el Apéndice B.

Generadores en marcha


En el Capítulo 4, derivamos una utilidad llamada run(...) que puede ejecutar generadores hasta su finalización, escuchando las Promesas cedidas (yield) y usándolas para reanudar asincrónamente el generador. asynquence tiene precisamente una utilidad de este tipo incorporada, llamada runner(...).

Primero preparemos algunos ayudantes para la ilustración:

Ahora, podemos usar runner(...) como un paso en medio de una secuencia:

Generadores Envueltos

También puede crear un generador autoempaquetado, es decir, una función normal que ejecute el generador especificado y devuelva una secuencia para su finalización, envolviéndola mediante ASQ.wrap(...):

Hay mucho más impresionante de lo que es capaz el runner(...), pero volveremos a ello en el Apéndice B.

Revisión


La asincuencia es una simple abstracción — una secuencia es una serie de pasos (asíncronos) — encima de Promesas, con el objetivo de hacer que el trabajo con varios patrones asíncronos sea mucho más fácil, sin ningún tipo de compromiso en la capacidad.

Hay otras cosas en la API del núcleo de asynquence y sus plug-ins de contribución más allá de lo que vimos en este apéndice, pero dejaremos eso como un ejercicio para que el lector vaya a comprobar el resto de las capacidades.

Ahora has visto la esencia y el espíritu de asynquence. La clave es que una secuencia se compone de pasos, y esos pasos pueden ser cualquiera de las docenas de diferentes variaciones de Promesas, o pueden ser un generador de ejecución, o…. La elección depende de usted, tiene toda la libertad para entrelazar cualquier lógica de control de flujo asíncrono que sea apropiada para sus tareas. Se acabó el cambio de biblioteca para detectar diferentes patrones de sincronización.

Si estos fragmentos asíncronos tienen sentido para ti, ahora estás bastante al día en la biblioteca; ¡no hace falta mucho para aprender, en realidad!

Si todavía tiene dudas sobre cómo funciona (¡o por qué!), querrá pasar un poco más de tiempo examinando los ejemplos anteriores y jugando con asynquence usted mismo, antes de pasar al siguiente apéndice. El Apéndice B empujará asynquence hacia varios patrones de asincronía más avanzados y poderosos.


3 thoughts on “YOU DON’T KNOW JS – 05: ASYNC & PERFOMANCE – APÈNDICE A: BIBLIOTECA AYNQUENCE en castellano”

  • 1
    Leonardo on 31 agosto, 2019 Responder

    Saludos mi estimado, espero este bien, le agradezco por su excelente trabajo, que tenga buen día.

    • 2
      Imobach on 1 septiembre, 2019 Responder

      Hola Leonardo, gracias por el comentario.

  • 3
    Mauricio Figueroa on 14 octubre, 2019 Responder

    buenas tardes estimado, la verdad que por mas vídeos que existan con cursos, siempre prefiero los libros, por lo que agradezco este gran trabajo que haz realizado!
    espero con ansias los link de descargas de los capítulos 5 de esta gran entrega.
    saludos cordiales.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *