Skip to content

Latest commit

 

History

History
271 lines (226 loc) · 14.7 KB

asynchronous-javascript-codelytv.md

File metadata and controls

271 lines (226 loc) · 14.7 KB

Table of Contents generated with DocToc

Asynchronous JavaScript Codely Course notes

Use cases for asynchronous JavaScript

  • Code example: problems with Promises
  • async/await dentro de un bucle, no funciona correctamente, el forEach no entiende de Promises, no devuelve promesas.
  • setTimeout() es una función asíncrona.
    • A delay of 0 does not mean that it will be executed immediately: the execution depends on the number of waiting tasks in the queue.
    • the second argument indicates a minimum time — not a guaranteed time.
  • Eventos, callbacks y promesas
    • Código de ejemplo: tipos de async

    • Los callbacks son funciones que pasamos como parámetro a otra función.

    • Los eventos nos permiten responder a un suceso de determinado tipo (con eventListeners).

    • Las promesas nos permiten actuar cuando un proceso asíncrono termina, sea con éxito o no. También nos aportan más legibilidad.

      fetch(`https://gutendex.com/books?search=${query}`).then((response) => response.json())

Promises: from callbacks to async/await

  • Example code: callbacks problems
  • Callback hell.
  • Evita el callback hell gracias a las promesas
  • Mejora la legibilidad: de promises a async/await
    • Code example
    • Async/await es otra forma de trabajar con promesas. En lugar de encadenar comportamientos usando .then(), podemos marcar una función como asíncrona con async, y esperar la resolución de la promesa con await para luego seguir con nuestro código de forma habitual.
    • await resuelve la promesa y la devuelve.
    • async/await is more imperative, easier to read.

Deep dive into Promises

  • Una promesa tiene varios estados:

    • pending: estado inicial, no se ha cumplido ni se ha rechazado.
    • fulfilled: la operación se ha completado con éxito.
    • rejected: la operación ha fallado.
    • settled: sería el estado que engloba fulfilled y rejected, es decir, cuando la promesa ya no está pending.
  • Tenemos varios métodos para reaccionar a los cambios de estado:

    • then(): se ejecuta cuando la promesa se resuelve, es decir, cuando pasa de pending a fulfilled.
    • catch(): se ejecuta cuando algo sale mal y la promesa se rechaza, es decir, cuando pasa de pending a rejected.
    • finally(): Se ejecuta cuando la promesa se resuelve o se rechaza, es decir, cuando pasa de pending a settled.
  • Controla los errores en tus promesas con reject:

  • ⛓️ Encadenar promises sin perder legibilidad

Event loop

  • JS tiene el event loop, que es como funciona la asincronía
  • Las cosas se van añadiendo a una pila que se va consumiendo.
  • La pila (Stack) de los callback y la pila de las promesas, son dos pilas distintas con prioridades distintas.
  • Function calls form a stack of frames.
  • Objects are allocated in a heap which is just a name to denote a large (mostly unstructured) region of memory.
  • A JavaScript runtime uses a message queue, which is a list of messages to be processed. Each message has an associated function that gets called to handle the message.
  • https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick#what-is-the-event-loop
function a() {
  console.log("1: soy el primer console log")

  setTimeout(function callback() {
    console.log("2: soy el console log del timeout")
  }, 0)

  console.log("3: soy el último console log")
}
  • La función a() se coloca en el stack y se ejecuta.
  • Primero, se ejecuta el primer console.log de forma síncrona. A continuación tenemos el setTimeout, que aunque tiene un timeout de 0 milisegundos sigue siendo una tarea asíncrona, por lo que JavaScript no ejecuta inmediatamente el código de dentro. En su lugar, lo coloca en una cola de tareas asíncrona, llamada macrotasks.
  • A continuación se ejecuta el console.log("3: soy el último console log"). En este momento el stack está vacío, por lo que el event loop mira si hay tareas pendientes en la cola de macrotasks. En este caso, pasa la función anónima del setTimeout al stack y la ejecuta.
  • Tipos de colas:
    • Cosas que estoy haciendo: call stack
    • Cosas pendientes de hacer: macrotasks
    • Cosas urgentes pendientes de hacer (e.g. promises): microtasks
  • El event loop trata las tareas de las promises de forma especial, moviéndolas a otra cola, la de microtasks.
  • Cuando el stack está vacío, el event loop primero mira si hay tareas pendientes en la cola de microtasks, encuentra el callback de la promise y lo pasa al stack. Cuando este vuelve a estar vacío, el event loop mira la cola de microtasks, ve que está vacía, por lo que mira la de macrotasks. Ahora sí, mueve el callback del setTimeout, lo pasa al stack y lo ejecuta.

Methods in Promise

  • Promise.all()

    • Código de ejemplo: promise-all to execute promises in parallel.

      Promise.all([getUserInfo(), getUserEnrolledCourses()]).then(([userInfo, enrolledCourses]) => {
          // ...
      });
      
  • Promise.allSettled()

    • Código de ejemplo
    • En el ejemplo anterior veíamos que en el Promise.all solo pasamos al then cuando todas las promesas del array se resuelven con éxito. Si no queremos que todo falle si solo falla una promesa, podemos usar Promise.allSettled y tratar el resultado según el status (fulfilled, rejected, pending, settled).
  • Promise.any()

    • Código de ejemplo
    • Si tenemos un array de promises y queremos quedarnos sólo con la primera que resuelva con éxito, podemos usar Promise.any
  • Promise.race()

    • Código de ejemplo
    • Similar al ejemplo anterior del Promise.any, pero en este caso se resolverá con la primera promesa que resuelva independientemente de si se resuelve con éxito o error. Por ejemplo, podemos forzar un timeout si una llamada tarda demasiado:
Promise.race([getUserList(), timeoutCheck(timeout)])
	.then(() => {
		console.log("🚀 Users found");
	})
	.catch(() => {
		console.log("⌛ Timeout error in getUserList");
	});

Deep dive into async/await

const books = await searchBookFromApi(req.query.search)

function searchBookFromApi(query) {
  return fetch(`https://gutendx.com/books?s=${query}`)
    .then((response) => response.json());
}
  • Unhandled promise rejection
    • Ejemplo de código

    • En general es buena práctica tratar las promesas correctamente, pero también tenemos listeners globales que nos permiten capturar cualquier error. En Node:

      process.on("unhandledRejection", () => {
      	console.error("Unexpected error");
      });
      
  • ¿Qué pasa si no quieres encadenar promesas?

Practical example of Promise

  • Evita peticiones innecesarias: cómo cancelar un fetch
    • Ejemplo de código: cancel fetch
    • E.g. en un campo de búsqueda, cancelar la búsqueda por "Fra" si has escrito "Frank"
    • Hay que poner un "throttle", un debounce para esperar antes de enviar peticiones (no hacerlo continuamente). En el ejemplo usa un timeout.
    • También cancelar fetch "antiguas", que ya no importen.
    • Podemos usar AbortController()
  • Bucles de promesas: ¿en paralelo o secuencial?
    • Ejemplo de código: loops and promises
    • With a for loop the promises are resolved sequentially
    • Promise.all(): el problema es que espera a la más lenta para resolver todo
    • La mejor UX: with a forEach loop the promises are resolved in parallel
  • Typescript y error en los catch: la magia del unknown
    • Ejemplo de código
    • En un catch() el error es any por defecto.
    • Sin embargo, usando try/catch y con el flag useUnknownInCatchVariables del tsconfig (con strict a true también), el tipado por defecto de los errores se establece a unknown obligando a los desarrolladores a hacer narrowing antes de acceder a las propiedades del error.
    • Narrowing
    • unknown

Testing async JS

  • Reduce el tiempo de ejecución de tus tests: falsea la asincronía con fake timers
    • Ejemplo de código: fake timers

    • podemos inicializar los fakeTimers al ejecutar los tests

      vi.useFakeTimers({
          shouldAdvanceTime: false
      })
      
      • y luego gracias a ello manipularlos a nuestro antojo: vi.advanceTimersByTime(5000)
      • Existe tanto en vitest como en jest.
  • Evitando errores por asincronía en tus tests de front

Other ways of async JS

  • Ejecuta scripts en segundo plano con Web Workers
    • Ejemplo de código: web workers

    • Aunque JavaScript es single thread es posible ejecutar procesos en paralelo dentro del browser gracias a los web workers.

    • Esta tecnología nos permite lanzar un proceso en un thread distinto al principal y tener una comunicación asíncrona con este para obtener los resultados de cálculos o procesos pesados.

    • Para registar un worker podemos hacerlo de la siguiente manera:: const myWorker = new Worker("worker.js", { type: "module" });

    • Una vez tenemos registrado un worker, podemos comunicarnos con este proceso de la siguiente manera.

      myWorker.postMessage(nPrime);
      
      myWorker.onmessage = (e) => {
      	printResult("nPrimeWorkerResult", nPrime, e.data);
      };
      
  • Evita imágenes rotas en tu web gracias a los Services Workers
    • Es un tipo de web worker que tiene funcionalides de red, e.g. para interceptar peticiones. Es una especie de proxy entre nuestro cliente web y la red.
    • Ejemplo de código
    • Es lo que usan las PWA por detrás (Progressive Web Apps)
    • Dentro del service worker que acabamos de registrar podemos interceptar el tráfico y cachear los recursos que queramos.
  • Comunicación entre clientes en tiempo real gracias a los Websockets
  • Buscador con fetch cancelable usando Rxjs

Resources

  • TBD