Programación asincrónica elegante con promesas.

¡Buen dia amigos!

Las promesas (promesas) son una característica relativamente nueva de JavaScript que le permite retrasar la ejecución de una acción hasta la finalización de la acción anterior o responder a la ejecución fallida de la acción. Esto contribuye a la correcta determinación de la secuencia de operaciones asincrónicas. Este artículo analiza cómo funcionan las promesas, cómo se usan en la API web y cómo puede escribir su propia promesa.

Condiciones: conocimientos básicos de informática, conocimiento de los conceptos básicos de JS.
Objetivo: comprender qué son las promesas y cómo se usan.

¿Qué son las promesas?


Revisamos brevemente las promesas en el primer artículo del curso, aquí las consideraremos con más detalle.

En esencia, una promesa es un objeto que representa un estado intermedio de una operación: "promete" que el resultado se devolverá en el futuro. No se sabe exactamente cuándo se completará la operación y cuándo se devolverá el resultado, pero hay una garantía de que cuando se complete la operación, su código hará algo con el resultado o manejará el error con gracia.

Como regla, cuánto tiempo lleva una operación asincrónica (¡no demasiado!) Nos interesa menos que la posibilidad de una respuesta inmediata a su finalización. Y, por supuesto, es bueno saber que el resto del código no está bloqueado.

Una de las promesas más comunes son las API web que devuelven promesas. Veamos una hipotética aplicación de video chat. La aplicación tiene una ventana con una lista de amigos del usuario, al hacer clic en el botón junto al nombre (o avatar) del usuario se inicia una videollamada.

El manejador de botones llama a getUserMedia ()para acceder a la cámara y el micrófono del usuario. Desde el momento en que getUserMedia () contacta al usuario para obtener permiso (para usar dispositivos, qué dispositivo usar si el usuario tiene varios micrófonos o cámaras, solo una llamada de voz, entre otras cosas), getUserMedia () espera no solo la decisión del usuario, sino también liberar dispositivos si están actualmente en uso. Además, el usuario puede no responder de inmediato. Todo esto puede conducir a grandes retrasos en el tiempo.

Si se realiza una solicitud de permiso para usar dispositivos desde el hilo principal, el navegador se bloquea hasta que se complete getUserMedia (). Es inaceptable Sin promesas, todo en el navegador se vuelve "no clicable" hasta que el usuario da permiso para usar el micrófono y la cámara. Por lo tanto, en lugar de esperar la decisión de un usuario y devolver un MediaStream para una secuencia creada a partir de fuentes (cámara y micrófono), getUserMedia () devuelve la promesa de que MediaStream procesa tan pronto como esté disponible.

El código de la aplicación de video chat podría verse así:

function handle CallButton(evt){
    setStatusMessage('Calling...')
    navigator.mediaDevices.getUserMedia({video: true, audio: true})
    .then(chatStream => {
        selfViewElem.srcObject = chatStream
        chatStream.getTracks().forEach(track => myPeerConnection.addTrack(track, chatStream))
        setStatusMessage('Connected')
    }).catch(err => {
        setStatusMessage('Failed to connect')
    })
}

La función comienza llamando a setStatusMessage (), mostrando el mensaje 'Llamando ...', que sirve como indicador de que se está intentando hacer una llamada. Luego se llama a getUserMedia (), solicitando una secuencia que contiene pistas de video y audio. Una vez que se forma la transmisión, se instala un elemento de video para mostrar la transmisión desde la cámara llamada 'vista propia', las pistas de audio se agregan a WebRTC RTCPeerConnection , que es una conexión a otro usuario. Después de eso, el estado se actualiza a 'Conectado'.

Si getUserMedia () falla, se inicia el bloque catch. Utiliza setStatusMessage () para mostrar un mensaje de error.

Tenga en cuenta que la llamada a getUserMedia () se devuelve incluso si la transmisión de video aún no se ha recibido. Incluso si la función handleCallButton () devolvió el control al código que lo llamó, tan pronto como getUserMedia () completara la ejecución, llamaría al controlador. Hasta que la aplicación "entienda" que la transmisión ha comenzado, getUserMedia () estará en modo de espera.

Nota: puede obtener más información al respecto en el artículo "Señales y videollamadas" . Este artículo proporciona un código más completo que el que usamos en el ejemplo.

Problema de funciones de devolución de llamada


Para comprender por qué las promesas son una buena solución, es útil observar la antigua forma de escribir devoluciones de llamada para ver cuáles eran sus problemas.

Como ejemplo, considere pedir pizza. Un pedido de pizza exitoso consta de varios pasos que se deben realizar en orden, uno tras otro:

  1. Elige el relleno. Esto puede llevar algo de tiempo si lo piensa durante mucho tiempo, y fracasará si cambia de opinión y pide un curry.
  2. Hacemos un pedido. Cocinar pizza lleva algo de tiempo y puede fallar si el restaurante carece de los ingredientes necesarios.
  3. Conseguimos pizza y comemos. Recibir pizza puede fallar si, por ejemplo, no podemos pagar el pedido.

Un pseudocódigo de estilo antiguo que utiliza funciones de devolución de llamada podría tener este aspecto:

chooseToppings(function(toppings){
    placeOrder(toppings, function(order){
        collectOrder(order, function(pizza){
            eatPizza(pizza)
        }, failureCallback)
    }, failureCallback)
}, failureCallback)

Este código es difícil de leer y mantener (a menudo se le llama el "infierno de las devoluciones de llamada" o el "infierno de las devoluciones de llamada"). La función failureCallback () debe llamarse en cada nivel de anidamiento. Hay otros problemas

Usamos promesas


Las promesas hacen que el código sea más limpio, más fácil de entender y apoyar. Si reescribimos el pseudocódigo usando promesas asincrónicas, obtenemos lo siguiente:

chooseToppings()
.then(function(toppings){
    return placeOrder(toppings)
})
.then(function(order){
    return collectOrder(order)
})
.then(function(pizza){
    eatPizza(pizza)
})
.catch(failureCallback)

Esto es mucho mejor: vemos lo que sucede, usamos un bloque .catch () para manejar todos los errores, la función no bloquea la transmisión principal (por lo que podemos jugar videojuegos mientras esperamos pizza), se garantiza que cada operación se realizará después de que se complete la anterior. Como cada promesa devuelve una promesa, podemos usar la cadena .then. Genial, verdad?

Usando las funciones de flecha, el pseudocódigo puede simplificarse aún más:

chooseToppings()
.then(toppings =>
    placeOrder(toppings)
)
.then(order =>
    collectOrder(order)
)
.then(pizza =>
    eatPizza(pizza)     
)
.catch(failureCallback)

O incluso así:

chooseToppings()
.then(toppings => placeOrder(toppings))
.then(order => collectOrder(order))
.then(pizza => eatPizza(pizza))
.catch(failureCallback)

Esto funciona porque () => x es idéntico a () => {return x}.

Incluso puede hacer esto (dado que las funciones simplemente pasan parámetros, no necesitamos capas):

chooseToppings().then(placeOrder).then(collectOrder).then(eatPizza).catch(failureCallback)

Dicho código es más difícil de leer y no puede usarse con construcciones más complejas que en pseudocódigo.

Nota: el pseudocódigo aún se puede mejorar usando async / await, que se discutirá en un artículo futuro.

En esencia, las promesas son similares a las de los oyentes de eventos, pero con algunas diferencias:

  • Una promesa se cumple solo una vez (éxito o fracaso). No se puede ejecutar dos veces o cambiar de éxito a fracaso o viceversa después de que se complete la operación.
  • Si se completa la promesa y agregamos una función de devolución de llamada para procesar su resultado, se llamará a esta función, a pesar de que el evento ocurrió antes de que se agregara la función.

Sintaxis de promesa básica: un ejemplo real


Conocer las promesas es importante porque muchas API web las usan en funciones que realizan tareas potencialmente complejas. Trabajar con tecnologías web modernas implica el uso de promesas. Más adelante aprenderemos cómo escribir nuestras propias promesas, pero por ahora, veamos algunos ejemplos simples que se pueden encontrar en la API web.

En el primer ejemplo, utilizamos el método fetch () para obtener la imagen de la red, el método blob () para convertir el contenido del cuerpo de respuesta en un objeto Blob y mostrar este objeto dentro del elemento <img> . Este ejemplo es muy similar al ejemplo del primer artículo , pero lo haremos un poco diferente.

Nota: el siguiente ejemplo no funcionará si simplemente lo ejecuta desde un archivo (es decir, usando file: // URL). Debe ejecutarlo a través de un servidor local o utilizando soluciones en línea como páginas de Glitch o GitHub .

1. En primer lugar, suba el HTML y la imagen que recibiremos.

2. Agregue el elemento <script> al final de <body> .

3. Dentro del elemento <script>, agregue la siguiente línea:

let promise = fetch('coffee.jpg')

El método fetch (), que toma la URL de la imagen como parámetro, se usa para obtener la imagen de la red. Como segundo parámetro, podemos especificar un objeto con configuraciones, pero por ahora nos limitaremos a una opción simple. Almacenamos la promesa devuelta por fetch () en la variable de promesa. Como se señaló anteriormente, una promesa es un objeto que representa un estado intermedio: el nombre oficial de un estado determinado está pendiente.

4. Para trabajar con el resultado de la finalización exitosa de la promesa (en nuestro caso, cuando la Respuesta regresa ), llamamos al método .then (). La función de devolución de llamada dentro del bloque .then () (a menudo denominado ejecutor) se inicia solo cuando la promesa se completa con éxito y se devuelve el objeto Respuesta: dicen que la promesa se ha completado (cumplido, cumplido). La respuesta se pasa como un parámetro.

Nota. La operación del bloque .then () es similar a la operación del detector de eventos AddEventListener (). Se activa solo después de que ocurra el evento (después de cumplir la promesa). La diferencia entre los dos es que .then () solo se puede llamar una vez, mientras que el oyente está diseñado para múltiples llamadas.

Después de recibir la respuesta, llamamos al método blob () para convertir la respuesta en un objeto Blob. Se parece a esto:

response => response.blob()

... que es una entrada corta para:

function (response){
    return response.blob()
}

OK, suficientes palabras. Agregue lo siguiente después de la primera línea:

let promise2 = promise.then(response => response.blob())

5. Cada llamada a .then () crea una nueva promesa. Esto es muy útil: dado que blob () también devuelve una promesa, podemos procesar el objeto Blob llamando a .then () en la segunda promesa. Dado que queremos hacer algo más complicado que llamar al método y devolver el resultado, necesitamos envolver el cuerpo de la función entre llaves (de lo contrario, se lanzará una excepción):

Agregue lo siguiente al final del código:

let promise3 = promise2.then(myBlob => {

})

6. Completemos el cuerpo de la función de ejecución. Agregue las siguientes líneas a las llaves:

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

Aquí llamamos al método URL.createObjectURL (), pasándolo como un parámetro Blob, que devolvió la segunda promesa. Obtenemos un enlace al objeto. Luego creamos un elemento <img>, establecemos el atributo src con el valor del enlace del objeto y lo agregamos al DOM para que la imagen aparezca en la página.

Si guarda el HTML y lo carga en un navegador, verá que la imagen se muestra como se esperaba. ¡Gran trabajo!

Nota. Probablemente haya notado que estos ejemplos son un tanto artificiales. Podría prescindir de los métodos fetch () y blob () y asignar la <img> URL correspondiente, coffee.jpg. Hicimos este camino para demostrar que trabajamos con promesas con un simple ejemplo.

Respuesta a la falla


Olvidamos algo: no tenemos un controlador de errores en caso de que una de las promesas falle (será rechazada). Podemos agregar un controlador de errores utilizando el método .catch (). Vamos a hacer eso:

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

Para ver esto en acción, especifique la URL de imagen incorrecta y vuelva a cargar la página. Aparecerá un mensaje de error en la consola.

En este caso, si no agregamos el bloque .catch (), también se mostrará un mensaje de error en la consola. Sin embargo, .catch () nos permite manejar los errores de la manera que queremos. En una aplicación real, su bloque .catch () puede intentar retomar la imagen o mostrar la imagen predeterminada, o pedirle al usuario que seleccione otra imagen o haga otra cosa.

Nota. Vea una demostración en vivo ( código fuente ).

Bloque de fusión


De hecho, el enfoque que usamos para escribir el código no es óptimo. Deliberadamente, tomamos este camino para que pueda comprender lo que está sucediendo en cada etapa. Como se mostró anteriormente, podemos combinar bloques .then () (y bloques .catch ()). Nuestro código se puede reescribir de la siguiente manera (también vea simple-fetch-chained.html en GitHub):

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)
})

Recuerde que el valor devuelto por la promesa se pasa como un parámetro al siguiente ejecutor de funciones del bloque .then ().

Nota. Los bloques .then () /. Catch () en las promesas son equivalentes asincrónicos del bloque try ... catch síncrono. Recuerde: prueba sincrónica ... catch no funcionará en código asincrónico.

Promesa Terminología Conclusiones


Hagamos un balance y escribamos una breve guía que puede usar en el futuro. Para consolidar el conocimiento, le recomendamos que lea la sección anterior varias veces.

1. Cuando se crea la promesa, dicen que está en un estado de expectativa.
2. Cuando se devuelve la promesa, dicen que se completó (resolvió):

  1. 1. Una promesa completada con éxito se llama cumplida. Devuelve el valor que se puede obtener a través de la cadena desde .then () al final de la promesa. La función de ejecución en el bloque .then () contiene el valor devuelto por la promesa.
  2. 2. Una promesa cumplida sin éxito se llama rechazada. Devuelve el motivo, el mensaje de error que condujo al rechazo de la promesa. Este motivo se puede obtener a través del bloque .catch () al final de la promesa.

Ejecute el código después de varias promesas.


Aprendimos los conceptos básicos del uso de promesas. Ahora veamos características más avanzadas. La cadena de .then () está bien, pero ¿qué pasa si queremos llamar a varios bloques de promesas uno por uno?

Puede hacerlo utilizando el método estándar Promise.all (). Este método toma una serie de promesas como parámetro y devuelve una nueva promesa cuando se cumplen todas las promesas de la matriz. Se ve algo como esto:

Promise.all([a,b,c]).then(values => {
        ...
})

Si se cumplen todas las promesas, el ejecutor de matriz del bloque .then () se pasará como un parámetro. Si al menos una de las promesas no se cumple, se rechazará todo el bloque.

Esto puede ser muy útil. Imagine que recibimos información para llenar dinámicamente la interfaz de usuario con contenido en nuestra página. En muchos casos, es más razonable recibir toda la información y mostrarla en la página más tarde que mostrar la información en partes.

Veamos otro ejemplo:
1. Descargue una plantilla de página y coloque la etiqueta <script> antes de la etiqueta de cierre </body>.

2. Descargue los archivos fuente ( coffee.jpg , tea.jpg y description.txt) o reemplácelos con los suyos.

3. En el script, primero definimos una función que devuelve las promesas que pasamos a Promise.all (). Esto será fácil de hacer si ejecutamos Promise.all () después de completar las tres operaciones fetch (). Podemos hacer lo siguiente:

let a = fetch(url1)
let b = fetch(url2)
let c = fetch(url3)

Promise.all([a, b, c]).then(values => {
    ...
})

Cuando se cumple la promesa, la variable "valores" contendrá tres objetos de respuesta, uno de cada operación fetch () completada.

Sin embargo, no queremos esto. No nos importa cuándo se completan las operaciones fetch (). Lo que realmente queremos son los datos cargados. Esto significa que queremos ejecutar el bloque Promise.all () después de recibir un blob válido que representa imágenes y cadenas de texto válidas. Podemos escribir una función que haga esto; agregue lo siguiente a su elemento <script>:

function fetchAndDecode(url, type){
    return fetch(url).then(response => {
        if(type === 'blob'){
            return response.blob()
        } else if(type === 'text'){
            return response.text()
        }
    }).catch(e => {
        console.log('There has been a problem with your fetch operation ' + e.message)
    })
}

Parece un poco complicado, así que veamos el código paso a paso:

1. En primer lugar, declaramos una función y le pasamos la URL y el tipo del archivo resultante.

2. La estructura de la función es similar a la que vimos en el primer ejemplo: llamamos a la función fetch () para obtener el archivo en una URL específica y luego transferir el archivo a otra promesa que devuelve el cuerpo de respuesta decodificado (leído). En el ejemplo anterior, siempre fue el método blob ().

3. Hay dos diferencias:

  • Primero, la segunda promesa de devolución depende del tipo de valor. Dentro de la función de ejecución, utilizamos el operador if ... else if para devolver la promesa según el tipo de archivo que necesitemos decodificar (en este caso, elegimos entre blob y texto, pero el ejemplo puede extenderse fácilmente para trabajar con otros tipos).
  • -, «return» fetch(). , (.. , blob() text(), , , ). , return .

4. Al final, llamamos al método .catch () para manejar cualquier error que pueda ocurrir en las promesas en la matriz pasada a .all (). Si se rechaza una de las promesas, el bloqueo de captura nos permitirá saber con qué promesa estuvo el problema. El bloque .all () aún se ejecutará, pero no mostrará los resultados para los que hubo errores. Si desea que se rechace .all (), agregue un bloque .catch () al final.

El código dentro de la función es asíncrono y se basa en promesas, por lo que toda la función funciona como una promesa.

4. Luego, llamamos a nuestra función tres veces para comenzar el proceso de recibir y decodificar imágenes y texto, y poner cada una de las promesas en una variable. Agregue lo siguiente:

let coffee = fetchAndDecode('coffee.jpg', 'blob')
let tea = fetchAndDecode('tea.jpg', 'blob')
let description = fetchAndDecode('description.txt', 'text')

5. Luego, declaramos un bloque Promise.all () para ejecutar algún código solo después de que se hayan cumplido las tres promesas. Agregue un bloque con una función ejecutor vacía dentro de .then ():

Promise.all([coffee, tea, description]).then(values => {

})

Puede ver que toma una serie de promesas como parámetro. El contratista comenzará solo después de que se cumplan las tres promesas; cuando esto sucede, el resultado de cada promesa (el cuerpo de respuesta decodificado) se colocará en una matriz, esto se puede representar como [resultados de café, resultados de té, resultados de descripción].

6. Finalmente, agregue lo siguiente al ejecutor (aquí usamos un código síncrono bastante simple para poner los resultados en variables (crear objetos URL desde blob) y mostrar imágenes y texto en la página):

console.log(values)
//       
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)

Guarde los cambios y vuelva a cargar la página. Debería ver que todos los componentes de la interfaz de usuario se están cargando, aunque esto no parece muy atractivo.

Nota. Si tiene alguna dificultad, puede comparar su versión del código con la nuestra: aquí hay una demostración en vivo y un código fuente .

Nota. Para mejorar este código, puede recorrer los elementos mostrados, recibir y decodificar cada uno, y luego recorrer los resultados dentro de Promise.all (), iniciando diferentes funciones para mostrar cada resultado dependiendo de su tipo. Esto le permitirá trabajar con cualquier cantidad de elementos.

Además, puede determinar el tipo de archivo resultante sin tener que especificar explícitamente la propiedad de tipo. Esto, por ejemplo, se puede hacer conresponse.headers.get ('tipo de contenido') para verificar el encabezado del tipo de contenido del protocolo HTTP.

Ejecute el código después de cumplir / rechazar la promesa


A menudo, es posible que deba ejecutar el código después de que se haya completado una promesa, independientemente de si se ejecutó o rechazó. Anteriormente, teníamos que incluir el mismo código tanto en el bloque .then () como en el bloque .catch (), por ejemplo:

myPromise
.then(response => {
    doSomething(response)
    runFinalCode()
})
.catch(e => {
    returnError(e)
    runFinalCode()
})

En los navegadores modernos, el método .finally () está disponible, lo que le permite ejecutar el código una vez que se completa la promesa, lo que evita la repetición y hace que el código sea más elegante. El código del ejemplo anterior se puede reescribir de la siguiente manera:

myPromise
.then(response => {
    doSomething(response)
})
.catch(e => {
    returnError(e)
})
.finally(() => {
    runFinalCode()
})

Puede ver el uso de este enfoque con un ejemplo real: la demostración de demostración en vivo-finally.html ( código fuente ). Funciona igual que Promise.all () del ejemplo anterior, excepto que agregamos finalmente () al final de la cadena en la función fetchAndDecode ():

function fetchAndDecode(url, type){
    return fetch(url).then(response => {
        if(type === 'blob'){
            return response.blob()
        } else if(type === 'text'){
            return response.text()
        }
    }).catch(e => {
        console.log(`There has been a problem with your fetch operation for resource "${url}": ${e.message}`)
    }).finally(() => {
        console.log(`fetch attempt for "${url}" finished.`)
    })
}

Recibiremos un mensaje sobre la finalización de cada intento de obtener el archivo.

Nota. then () / catch () / finally () es el equivalente asíncrono de try sincrónico () / catch () / finally ().

Escribiendo tu propia promesa


La buena noticia es que ya sabes cómo hacerlo. Cuando combina múltiples promesas con .then (), o las combina para proporcionar una funcionalidad específica, crea sus propias funciones asíncronas y basadas en promesas. Tomemos, por ejemplo, nuestra función fetchAndDecode () del ejemplo anterior.

La combinación de varias API basadas en promesas para crear una funcionalidad específica es la forma más común de trabajar con las promesas, lo que demuestra la flexibilidad y el poder de las API modernas. Sin embargo, hay otra manera.

Promesa del constructor ()


Puede crear su propia promesa utilizando el constructor Promise (). Esto puede ser necesario en una situación en la que tiene código de la antigua API asincrónica que necesita "sobredimensionar". Esto se hace para poder trabajar simultáneamente tanto con el código existente, antiguo, bibliotecas o "marcos", como con un nuevo código basado en promesas.

Veamos un ejemplo simple: aquí envolvemos la llamada setTimeout () en una promesa: esto iniciará la función en 2 segundos, que cumplirá la promesa (usando la llamada resolve () aprobada) con la cadena "¡Éxito!".

let timeoutPromise = new Promise((resolve, reject) => {
    setTimeout(function(){
        resolve('Success!')
    }, 2000)
})

resolver () y rechazar () son funciones que están llamadas a cumplir o rechazar una nueva promesa. En este caso, la promesa se cumple con la cadena "¡Éxito!".

Cuando llama a esta promesa, puede agregarle un bloque .then () para seguir trabajando con la línea "¡Éxito!". Entonces podemos generar una línea en el mensaje:

timeoutPromise
.then((message) => {
    alert(message)
})

... más o menos:

timeoutPromise.then(alert)

Vea una demostración en vivo ( código fuente ).

El ejemplo dado no es muy flexible: la promesa solo se puede cumplir con una línea simple, no tenemos un controlador de errores: rechazar () (realmente setTimeout () no necesita un controlador de errores, por lo que en este caso no importa).

Nota. ¿Por qué resolver () en lugar de cumplir ()? Por el momento, la respuesta es esta: es difícil de explicar.

Trabajamos rechazando una promesa.


Podemos crear una promesa rechazada utilizando el método rechazar (): al igual que resolver (), rechazar () toma un valor simple, pero a diferencia de resolver (), el valor simple no es el resultado, sino la razón del rechazo, es decir. Un error que se pasa al bloque .catch ().

Expandamos el ejemplo anterior agregando una condición para rechazar una promesa, así como la capacidad de recibir mensajes que no sean de éxito.

Tome el ejemplo anterior y vuelva a escribirlo así:

function timeoutPromise(message, interval){
    return new Promise((resolve, reject) => {
        if(message === '' || typeof message !== 'string'){
            reject('Message is empty or not a string')
        } else if(interval < 0 || typeof interval !== number){
            reject('Interval is negative or not a number')
        } else{
            setTimeout(function(){
                resolve(message)
            }, interval)
        }
    })
}

Aquí pasamos dos argumentos a la función: mensaje e intervalo (retraso de tiempo). En una función, devolvemos un objeto Promise.

En el constructor Promise, realizamos varias comprobaciones utilizando las estructuras if ... else:

  1. Primero, verificamos el mensaje. Si está vacío o no es una cadena, rechazamos la promesa e informamos un error.
  2. En segundo lugar, verificamos el retraso de tiempo. Si es negativo o no un número, también rechazamos la promesa e informamos un error.
  3. Finalmente, si ambos argumentos están bien, muestre el mensaje después de un cierto tiempo (intervalo) usando setTimeout ().

Como timeoutPromise () devuelve una promesa, podemos agregarle .then (), .catch (), etc. para mejorar su funcionalidad. Vamos a hacer eso:

timeoutPromise('Hello there!', 1000)
.then(message => {
    alert(message)
}).catch(e => {
    console.log('Error: ' + e)
})

Después de guardar los cambios y ejecutar el código, verá un mensaje después de un segundo. Ahora intente pasar una cadena vacía como mensaje o un número negativo como intervalo. Verá un mensaje de error como resultado de rechazar la promesa.

Nota. Puede encontrar nuestra versión de este ejemplo en GitHub: custom-promise2.html ( fuente ).

Un ejemplo más realista.


El ejemplo que examinamos facilita la comprensión del concepto, pero no es realmente asíncrono. La asincronía en el ejemplo se simula usando setTimeout (), aunque todavía muestra la utilidad de las promesas para crear funciones con un flujo de trabajo razonable, buen manejo de errores, etc.

Un ejemplo de una aplicación asíncrona útil que utiliza el constructor Promise () es la biblioteca idb (IndexedDB con usabilidad)Jake Archibald. Esta biblioteca le permite utilizar la API IndexedDB (basada en las funciones de devolución de llamada API para almacenar y recuperar datos en el lado del cliente), escrita en el estilo antiguo, con promesas. Si observa el archivo de la biblioteca principal, verá que utiliza las mismas técnicas que examinamos anteriormente. El siguiente código convierte el modelo de consulta básico utilizado por muchos métodos IndexedDB en un modelo compatible prometedor:

function promisifyRequest(request){
    return new Promise(function(resolve, reject){
        request.onsuccess = function(){
            resolve(request.result)
        }

        request.onerror = function(){
            reject(request.error)
        }
    })
}

Esto agrega un par de controladores de eventos que cumplen o rechazan la promesa, según corresponda:

  • Cuando la solicitud tiene éxito , el controlador de éxito cumple la promesa con el resultado de la solicitud.
  • Cuando la solicitud falla , el controlador onerror rechaza la promesa con el error de la solicitud.

Conclusión


Las promesas son una excelente manera de crear aplicaciones asincrónicas cuando no sabemos qué valor devolverá la función o cuánto tiempo llevará. Le permiten trabajar con una secuencia de operaciones asincrónicas sin utilizar funciones de devolución de llamada profundamente anidadas, y admiten el estilo de manejo de errores de la declaración try ... catch síncrona.

Las promesas son compatibles con todos los navegadores modernos. La única excepción es Opera Mini e IE11 y sus versiones anteriores.

En este artículo, hemos considerado lejos de todas las características de las promesas, solo las más interesantes y útiles. Conocerá otras características y técnicas si desea obtener más información sobre las promesas.

La mayoría de las API web modernas se basan en promesas, por lo que es necesario conocerlas. Entre estas API web podemos nombrar WebRTC , Web Audio API , Media Capture y Streams , etc. Las promesas se volverán cada vez más populares, por lo que su estudio y comprensión es un paso importante hacia el dominio de JavaScript moderno.

Consulte también "Errores comunes en las promesas de JavaScript que todos deberían conocer" .

Gracias por su atención.

La crítica constructiva es bienvenida.

All Articles