Cómo funcionan las devoluciones de llamada, las promesas y la espera asíncrona - JavaScript asíncrono
JavaScript asíncrono
JavaScript promociona la programación asincrónica como una característica. Esto significa que, si alguna acción toma un tiempo, su programa puede continuar haciendo otras cosas mientras se completa la acción. Una vez que se realiza esa acción, puede hacer algo con el resultado. Esto resulta ser una gran característica para la funcionalidad como la obtención de datos, pero puede ser confuso para los recién llegados. En JavaScript, tenemos algunas formas diferentes de manejar la asincronía: funciones de devolución de llamada, promesas y async-wait.
Funciones de devolución de llamada
Una función de devolución de llamada es una función que usted proporciona que se ejecutará después de completar la operación asincrónica. Creemos un buscador de datos de usuario falso y usemos una función de devolución de llamada para hacer algo con el resultado.
El buscador de datos falsos
Primero creamos un buscador de datos falsos que no toma una función de devolución de llamada. Como fakeData
no existe durante 300 milisegundos, no tenemos acceso sincrónico a él.
const fetchData = userId => {
setTimeout(() => {
const fakeData = {
id: userId,
name: 'George',
};
// Our data fetch resolves
// After 300ms. Now what?
}, 300);
};
¡Para poder hacer algo con nuestro fakeData
, podemos pasar fetchData
una referencia a una función que manejará nuestros datos!
const fetchData = (userId, callback) => {
setTimeout(() => {
const fakeData = {
id: userId,
name: 'George',
};
callback(fakeData);
}, 300);
};
Creemos una función de devolución de llamada básica y probémosla:
const cb = data => {
console.log("Here's your data:", data);
};
fetchData(5, cb);
Después de 300 ms, deberíamos ver lo siguiente registrado:
Here's your data: {id: 5, name: "George"}
Promesas
El objeto Promise representa la finalización eventual de una operación en JavaScript. Las promesas pueden resolve
o bien reject
. Cuando se resuelve una Promesa, puede manejar su valor devuelto con el método. Si se rechaza una Promesa, puede utilizar la captura del error y manejarlo.
La sintaxis del objeto Promise es la siguiente:
new Promise(fn);
Were fn
es una función que toma una resolve
función y, opcionalmente, una reject
función.
fn = (resolve, reject) => {};
El buscador de datos falsos (con promesas)
Usemos el mismo buscador de datos falsos que antes. En lugar de pasar una devolución de llamada, vamos a devolver un nuevo Promise
objeto que se resuelve con los datos de nuestro usuario después de 300 ms. Como beneficio adicional, también podemos darle una pequeña posibilidad de rechazo.
const fetchData = userId => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < 0.1) {
reject('Fetch failed!');
}
const fakeData = {
id: userId,
name: 'George',
};
resolve(fakeData);
}, 300);
});
};
Nuestra nueva fetchData
función se puede utilizar de la siguiente manera:
fetchData(5)
.then(user => {
console.log("Here's your data:", user);
})
.catch(err => {
console.error(err);
});
Si fetchData
se resuelve con éxito (esto sucederá el 90% del tiempo), registraremos nuestros datos de usuario como lo hicimos con la solución de devolución de llamada. Si se rechaza, aparecerá console.error
el mensaje de error que creamos ("¡Fetch falló!")
Una cosa buena de Promesas es que puedes encadenarlas para ejecutar Promesas posteriores. Por ejemplo, podríamos hacer algo como esto:
fetchData(5)
.then(user => {
return someOtherPromise(user);
})
.then(data => {
console.log(data);
})
.catch(err => {
console.error(err);
});
Además, podemos pasar una serie de Promesas Promise.all
a solo tomar medidas después de que se resuelvan todas las Promesas:
Promise.all([fetchData(5), fetchData(10)])
.then(users => {
console.log("Here's your data:", users);
})
.catch(err => {
console.error(err);
});
En este caso, si ambas promesas se resuelven con éxito, se registrará lo siguiente:
Here's your data:
[{ id: 5, name: "George" }, { id: 10, name: "George" }]
Async-Await
Async-await ofrece una sintaxis diferente para escribir promesas que algunos encuentran más claras. Con async-await, puede crear una async
función. Dentro de esa función asíncrona, ¡puede ser await
el resultado de una Promesa antes de ejecutar el código posterior! Veamos nuestro ejemplo de búsqueda de datos.
const fetchUser = async userId => {
const user = await fetchData(userId);
console.log("Here's your data:", user);
};
fetchUser(5);
Bastante bien, ¿verdad? Una pequeña arruga: no estamos manejando nuestro caso de rechazo de Promise. Podemos hacer esto con try/catch
.
const fetchUser = async userId => {
try {
const user = await fetchData(userId);
console.log("Here's your data:", user);
} catch (err) {
console.error(err);
}
};
fetchUser(5);
Navegador / Soporte de nodo
Dado que las funciones de devolución de llamada son funciones normales que se pasan a otras funciones, no hay preocupación por el soporte. Las promesas han sido estándar desde ECMAScript 2015 y tienen un soporte decente, pero no son compatibles con Internet Explorer. Async-await es más nuevo (estándar desde ECMAScript 2017) y tiene un buen soporte en las versiones más nuevas del navegador. Nuevamente, no es compatible con Internet Exporer.
En el lado del nodo, async-wait (y, por lo tanto, Promises) han sido bien respaldados desde la versión v7.6 de noviembre.