Este post es una transcripción para estudiar de este vídeo de Fireship 🔥🔥🔥
Cómo funciona el event loop?
Tanto el browser como NodeJS corren siempre un hilo de proceso.
En una vuelta van a correr todo el código sincrónico. Pueden haber eventos asincrónicos que se correran luego como macrotareas y micro tareas.
Las macro tareas se ejecutan al comienzo del próximo ciclo del hilo. Las microtareas (p.e. promesas) se ejecutan antes de que el ciclo presente termine.
Promesas
fetch: es un API del navegador que ‘pide’ a una dirección y nos deja ver cuál es la respuesta (como una promesa).
const url = 'https://jsonplaceholder.typicode.com/todos/1' const promesa = fetch(url)
Promesa no va a tener el valor de la respuesta hasta que no sea resuelta (es asincrónica) por lo que tenemos que ponerla en cola (queue, o esperar a que se resuelva)
const promesa = fetch(url); promesa.then( res => res.json() ); // mapear a json tambien es una promesa porque puede // tomar mucho tiempo
Una caracteristica de esta promesas encadenadas (primero el fetch, luego el json, luego otra cosa) es que se pueden ‘desencadenar’
promesa. then( res => res.json() ). then( user => console.log('😀', user.title ) )
Por ejemplo, atrapar un error durante la resolución de las promesas es una promesa especial llamada catch
promesa. then( res => res.json() ). then( user => console.log('😀', user.title ) ). catch(err => console.error('😰',err))
Non-Blocking code
Imagina esto:
const Bloqueadora = () => { let i =0; while(i<1000000000) i++; return 'ya terminé de entorpecer tu hilo de proceso' }
Esta tarea frenará el hilo de proceso hasta que culmine de contar. Como es algo que nos va a retrasar, podemos hacerla una promesa, y que retorne luego en el futuro
const Bloqueadora = () => { return new Promise((resolve, reject) => { let i =0; while(i<1000000000) i++; return 'ya terminé de entorpecer tu hilo de proceso' }) }
Pero hacerlo de esta forma está metiendo a la promesa en el ciclo principal. Lo que necesitamos que esté en el ciclo es la resolución de la promesa.
const Bloqueadora = () => { return Promise.resolve().then( x => { let i =0; while(i<1000000000) i++; return 'ya terminé de entorpecer tu hilo de proceso' })
Async / await y la magia de ES6 🔮🔮🔮
async y await son una forma de hacer más bonita la sintaxis de promesas, sobre todo cuando hay promesas encadenadas:
para evitar esto se pueden crear funciones que retornen promesas así:
const getFruta = async (name) => { const fruta = { pineapple: '🍍', peach: '🍑', strawberry: '🍓' } await delay(1000) return fruta[name]; } const hasJugo = async () => { const a = await getFruta('pineapple'); const b = await getFruta('peach'); return [a, b]; }
Pero esto introduce otro problema, que ahora hasJugo está creando dos promesas consecutivas, en vez de ser paralelas. Para hacer promesas concurrentes no hay magia :(:
const hasJugo = async () => { const a = getFruta('pineapple'); const b = getFruta('peach'); return Promise.all([a, b]); }
Ese Promise.all hace que todas las promesas se realicen de manera concurrente.
Normas de buena educación
Es de buena educación que toda la promesa tenga manejo de posibles errores:
const hasJugo = async () => { try{ const a = getFruta('pineapple'); const b = getFruta('peach'); throw 'la fruta estaba rancia!!!' return Promise.all([a, b]); } catch(err){ console.error(☠,err) //y aca se puede o lanzar un error ó //retornar un mensaje //return 'el cliente no se va a dar cuenta' //throw 'sabe a 💩' //return: no modifica la lógica del programa //throw: el programa tendrá que hacer un catch } }
Tips de cierre:
hacer await dentro de un map, o cualquiera de esas higher order functions no espera a la resolución de la promesa, la deja ejecutando concurrentemente.
Si se quiere que un loop espere a la resolución de cada promesa antes de continuar con la siguiente se debe hacer un ciclo for:
const fruits = ['peach', 'pineapple', 'strawberry'] const getPromesa = async () => { for(const f of fruits){ const emoji = await getFruta(f); console.log(emoji) } }
si se quiere que la cosa sea concurrente, se puede usar el map (que dije que no se usaba)
const fruits = ['peach', 'pineapple', 'strawberry'] const promesas = fruits.map(v=>getFruta(v)); const forFrutas= async () => { for await (const fruta of fruits){ console.log(fruta) } }
o se pueden ejecutar ciertas partes del programa a que esperen la resolución de una promesa sin bloquear así:
const inspection = async () ={ if(await getFruit('peach') === '🍑'){ console.log('tiene forma de pompis') } }