Programación asincrónica con asíncrono / espera

¡Buen dia amigos!

Las adiciones relativamente nuevas de JavaScript son funciones asincrónicas y la palabra clave de espera. Estas características son básicamente azúcar sintáctica sobre promesas (promesas), lo que facilita la escritura y lectura de código asincrónico. Hacen que el código asincrónico parezca síncrono. Este artículo te ayudará a descubrir qué es qué.

Condiciones: conocimientos básicos de informática, conocimiento de los conceptos básicos de JS, comprensión de los conceptos básicos de código asíncrono y promesas.
Propósito: Comprender cómo se hacen las promesas y cómo se usan.

Conceptos básicos de async / wait


Usar async / await tiene dos partes.

Palabra clave asíncrona


En primer lugar, tenemos la palabra clave asíncrona, que colocamos antes de la declaración de función para que sea asíncrona. Una función asincrónica es una función que anticipa la capacidad de usar la palabra clave wait para ejecutar código asincrónico.

Intente escribir lo siguiente en la consola del navegador:

function hello(){ return 'Hello' }
hello()

La función devolverá 'Hola'. Nada inusual, ¿verdad?

Pero, ¿qué pasa si lo convertimos en una función asincrónica? Intenta lo siguiente:

async function hello(){ return 'Hello' }
hello()

Ahora una llamada a función devuelve una promesa. Esta es una de las características de las funciones asincrónicas: devuelven valores que se garantiza que se convertirán en promesas.

También puede crear expresiones funcionales asíncronas, de esta manera:

let hello = async function(){ return hello() }
hello()

También puede usar las funciones de flecha:

let hello = async () => { return 'Hello' }

Todas estas funciones hacen lo mismo.

Para obtener el valor de una promesa completada, podemos usar el bloque .then ():

hello().then((value) => console.log(value))

... o aun así:

hello().then(console.log)

Por lo tanto, agregar la palabra clave asíncrona hace que la función devuelva una promesa en lugar de un valor. Además, permite funciones síncronas para evitar cualquier sobrecarga asociada con la ejecución y el soporte del uso de wait. Una simple adición de asíncrono frente a la función permite la optimización automática del código por parte del motor JS. ¡Frio!

Palabra clave aguarda


Los beneficios de las funciones asincrónicas se hacen aún más evidentes cuando las combina con la palabra clave wait. Se puede agregar antes de cualquier función basada en la promesa para hacerla esperar a que se complete la promesa y luego devolver el resultado. Después de eso, se ejecuta el siguiente bloque de código.

Puede usar esperar cuando llame a cualquier función que devuelva una promesa, incluidas las funciones de API web.

Aquí hay un ejemplo trivial:

async function hello(){
    return greeting = await Promise.resolve('Hello')
}

hello().then(alert)

Por supuesto, el código anterior es inútil, solo sirve como una demostración de la sintaxis. Sigamos y veamos un ejemplo real.

Reescritura de código en promesas usando async / await


Tome el ejemplo de recuperación del artículo anterior:

fetch('coffee.jpg')
.then(response => response.blob())
.then(myBlob => {
    let objectURL = URL.createObjectURL(myBlob)
    let image = document.createElement('img')
    image.src = objectURL
    document.body.appendChild(image)
})
.catch(e => {
    console.log('There has been a problem with your fetch operation: ' + e.message)
})

Ya debe comprender qué promesas son y cómo funcionan, pero reescribamos este código usando async / wait para ver cuán simple es:

async function myFetch(){
    let response = await fetch('coffee.jpg')
    let myBlob = await response.blob()

    let objectURL = URL.createObjectURL(myBlob)
    let image = document.createElement('img')
    image.src = objectURL
    document.body.appendChild(image)
}

myFetch().catch(e => {
    console.log('There has been a problem with your fetch operation: ' + e.message)
})

Esto hace que el código sea mucho más simple y fácil de entender, ¡sin bloques .then ()!

El uso de la palabra clave asíncrona convierte una función en una promesa, por lo que podemos usar un enfoque mixto de las promesas y esperar, destacando la segunda parte de la función en un bloque separado para aumentar la flexibilidad:

async function myFetch(){
    let response = await fetch('coffee.jpg')
    return await response.blob()
}

myFetch().then((blob) => {
    let objectURL = URL.createObjectURL(blob)
    let image = document.createElement('image')
    image.src = objectURL
    document.body.appendChild(image)
}).catch(e => console.log(e))

Puede volver a escribir el ejemplo o ejecutar nuestra demostración en vivo (consulte también el código fuente ).

pero como funciona?


Envolvimos el código dentro de la función y agregamos la palabra clave asíncrona antes de la palabra clave de función. Debe crear una función asincrónica para determinar el bloque de código en el que se ejecutará el código asincrónico; await solo funciona dentro de funciones asincrónicas.

Una vez más: esperar solo funciona en funciones asincrónicas.

Dentro de la función myFetch (), el código es muy parecido a la versión de las promesas, pero con algunas diferencias. En lugar de usar el bloque .then () después de cada método basado en promesas, simplemente agregue la palabra clave wait antes de llamar al método y asigne un valor a la variable. La palabra clave wait hace que el motor JS pause la ejecución del código en una línea determinada, permitiendo que se ejecute otro código hasta que la función asincrónica devuelva un resultado. Una vez que se ejecuta, el código continuará ejecutándose desde la siguiente línea.
Por ejemplo:

let response = await fetch('coffee.jpg')

El valor devuelto por la promesa fetch () se asigna a la variable de respuesta cuando el valor dado está disponible, y el analizador se detiene en esa línea hasta que se completa la promesa. Tan pronto como el valor esté disponible, el analizador pasa a la siguiente línea de código que crea el Blob. Esta línea también llama al método asincrónico basado en promesas, por lo que aquí también usamos wait. Cuando el resultado de la operación regresa, lo devolvemos desde la función myFetch ().

Esto significa que cuando llamamos a la función myFetch (), devuelve una promesa, por lo que podemos agregarle .then (), dentro de la cual manejamos la visualización de la imagen en la pantalla.

Probablemente piense "¡Eso es genial!" Y tiene razón: menos bloques .then () para envolver el código, todo parece código síncrono, por lo que es intuitivo.

Agregar manejo de errores


Si desea agregar manejo de errores, tiene varias opciones.

Puede usar la estructura de prueba sincrónica ... catch con async / await. Este ejemplo es una versión extendida del código anterior:

async function myFetch(){
    try{
        let response = await fetch('coffee.jpg')
        let myBlob = await response.blob()

        let objectURL = URL.createObjectURL(myBlob)
        let image = document.createElement('img')
        image.src = objectURL
        document.body.appendChild(image)
    } catch(e){
        console.log(e)
    }
}

myFetch()

El bloque catch () {} acepta un objeto de error, que llamamos "e"; Ahora podemos enviarlo a la consola, esto nos permitirá recibir un mensaje sobre dónde ocurrió el error en el código.

Si desea usar la segunda versión del código que se muestra arriba, simplemente debe continuar usando el enfoque híbrido y agregar el bloque .catch () al final de la llamada .then (), de la siguiente manera:

async function myFetch(){
    let response = await fecth('coffee.jpg')
    return await response.blob()
}

myFetch().then((blob) => {
    let objectURL = URL.createObjectURL
    let image = document.createElement('img')
    image.src = objectURL
    document.body.appendChild(image)
}).catch(e => console.log(e))

Esto es posible porque el bloque .catch () detectará los errores que ocurren tanto en la función asincrónica como en la cadena de promesa. Si usa el bloque try / catch aquí, no podrá manejar los errores que ocurren cuando se llama a la función myFetch ().

Puede encontrar ambos ejemplos en GitHub:
simple-fetch-async-await-try-catch.html (ver fuente )

simple-fetch-async-await-promise-catch.html (ver fuente )

Esperando Promise.all ()


Async / await se basa en promesas, por lo que puede aprovechar al máximo esta última. Estos incluyen Promise.all () en particular: puede agregar fácilmente wait a Promise.all () para escribir todos los valores de retorno de manera similar al código síncrono. Nuevamente, tome el ejemplo del artículo anterior . Mantenga una pestaña abierta para comparar con el código que se muestra a continuación.

Con async / await (ver demostración en vivo y código fuente ) se ve así:

async function fetchAndDecode(url, type){
    let repsonse = await fetch(url)

    let content

    if(type === 'blob'){
        content = await response.blob()
    } else if(type === 'text'){
        content = await response.text()
    }

    return content
}

async function displayContent(){
    let coffee = fetchAndDecode('coffee.jpg', 'blob')
    let tea = fetchAndDecode('tea.jpg', 'blob')
    let description = fetchAndDecode('description.txt', 'text')

    let values = await Promise.all([coffee, tea, description])

    let objectURL1 = URL.createObjectURL(values[0])
    let objectURL2 = URL.createObjectURL(values[1])
    let descText = values[2]

    let image1 = document.createElement('img')
    let image2 = document.createElement('img')
    image1.src = objectURL1
    image2.src = objectURL2
    document.body.appendChild(image1)
    document.body.appendChild(image2)

    let para = document.createElement('p')
    para.textContent = descText
    document.body.appendChild(para)
}

displayContent()
.catch(e => console.log(e))

Fácilmente hicimos que la función fetchAndDecode () fuera asíncrona con un par de cambios. Presta atención a la línea:

let values = await Promise.all([coffee, tea, description])

Usando wait, obtenemos los resultados de tres promesas en la variable de valores, de manera similar al código síncrono. Debemos envolver toda la función en una nueva función asincrónica, displayContent (). No logramos una fuerte reducción de código, pero pudimos extraer la mayor parte del código del bloque .then (), lo que proporciona una simplificación útil y hace que el código sea más legible.

Para manejar errores, agregamos un bloque .catch () a nuestra llamada a displayContent (); Maneja errores de ambas funciones.

Recuerde: también puede usar el bloque .finally () para obtener un informe sobre la operación; puede verlo en acción en nuestra demostración en vivo (consulte también el código fuente ).

Desventajas asíncrono / espera


Async / await tiene un par de fallas.

Async / await hace que el código parezca síncrono y, en cierto sentido, hace que se comporte de forma más sincrónica. La palabra clave await bloquea la ejecución del código que le sigue hasta que se completa la promesa, como sucede en una operación sincrónica. Esto le permite realizar otras tareas, pero su propio código está bloqueado.

Esto significa que su código puede ser ralentizado por una gran cantidad de promesas pendientes una tras otra. Cada espera esperará a que se complete la anterior, mientras que nos gustaría que las promesas se cumplieran simultáneamente, como si no estuviéramos usando async / await.

Hay un patrón de diseño para mitigar este problema: deshabilitar todos los procesos de promesa almacenando objetos Promesa en variables y luego esperándolos. Veamos cómo se implementa esto.

Tenemos dos ejemplos a nuestra disposición: slow-async-await.html (ver código fuente ) y fast-async-await.html (ver código fuente ). Ambos ejemplos comienzan con una función de promesa que imita una operación asincrónica usando setTimeout ():

function timeoutPromise(interval){
    return new Promise((resolve, reject) => {
        setTimeout(function(){
            resolve('done')
        }, interval)
    })
}

Luego sigue la función asincrónica timeTest (), que espera tres llamadas a timeoutPromise ():

async function timeTest(){
    ...
}

Cada una de las tres llamadas a timeTest () finaliza con un registro del tiempo que tardó en cumplir la promesa, luego se registra el tiempo que lleva completar toda la operación:

let startTime = Date.now()
timeTest().then(() => {
    let finishTime = Date.now()
    let timeTaken = finishTime - startTime
    alert('Time taken in milliseconds: ' + timeTaken)
})

En cada caso, la función timeTest () es diferente.

En slow-async-await.html timeTest () tiene este aspecto:

async function timeTest(){
    await timeoutPromise(3000)
    await timeoutPromise(3000)
    await timeoutPromise(3000)
}

Aquí solo esperamos tres llamadas a timeoutPromise, cada vez configurando un retraso de 3 segundos. Cada llamada espera la finalización de la anterior; si ejecuta el primer ejemplo, verá una ventana modal en aproximadamente 9 segundos.

En fast-async-await.html timeTest () se ve así:

async function timeTest(){
    const timeoutPromise1 = timeoutPromise(3000)
    const timeoutPromise2 = timeoutPromise(3000)
    const timeoutPromise3 = timeoutPromise(3000)

    await timeoutPromise1
    await timeoutPromise2
    await timeoutPromise3
}

Aquí guardamos tres objetos Promise en variables, lo que hace que los procesos asociados se ejecuten simultáneamente.

Además, esperamos sus resultados: a medida que las promesas comiencen a cumplirse simultáneamente, las promesas también se completarán al mismo tiempo; cuando ejecute el segundo ejemplo, verá una ventana modal en aproximadamente 3 segundos.

Debe probar cuidadosamente el código y tener esto en cuenta mientras reduce el rendimiento.

Otro inconveniente menor es la necesidad de envolver las promesas esperadas en una función asincrónica.

Usando async / await con clases


En conclusión, notamos que puede agregar asíncrono incluso en métodos para crear clases para que devuelvan promesas y esperen promesas dentro de ellas. Tome el código del artículo sobre JS orientado a objetos y compárelo con la versión modificada usando async:

class Person{
    constructor(first, last, age, gender, interests){
        this.name = {
            first,
            last
        }
        this.age = age
        this.gender = gender
        this.interests = interests
    }

    async greeting(){
        return await Promise.resolve(`Hi! I'm ${this.name.first}`)
    }

    farewell(){
        console.log(`${this.name.first} has left the building. Bye for now!`)
    }
}

let han = new Person('Han', 'Solo', 25, 'male', ['Smuggling'])

El método de clase se puede usar de la siguiente manera:

han.greeting().then(console.log)

Soporte del navegador


Uno de los obstáculos para usar async / await es la falta de soporte para navegadores antiguos. Esta característica está disponible en casi todos los navegadores modernos, así como en las promesas; Existen algunos problemas en Internet Explorer y Opera Mini.

Si desea usar async / await, pero necesita el soporte de navegadores más antiguos, puede usar la biblioteca BabelJS : le permite usar el último JS, convirtiéndolo en uno específico para el navegador.

Conclusión


Async / await le permite escribir código asincrónico que es fácil de leer y mantener. Aunque async / await es peor que otras formas de escribir código asincrónico, definitivamente vale la pena explorarlo.

Gracias por su atención.

¡Feliz codificación!

All Articles