Consejos para dominar promesas en programación: técnicas avanzadas

???? Descubre técnicas avanzadas para aprovechar al máximo las promesas en proyectos JavaScript. Conoce consejos y trucos para mejorar tu desarrollo. ¡Feliz programación! ✨

???? En los proyectos JavaScript, aprovechar las promesas es crucial. Sorprendentemente, he observado que muchos desarrolladores frontend intermedios y experimentados, tanto entre colegas como entrevistadores, tienden a apegarse a prácticas convencionales como promiseInst.then(), promiseInst.catch(), y Promise.

Incluso con async/await, algunos lo utilizan sin una comprensión profunda de sus matices.

???? Sin embargo, las promesas ofrecen una plétora de casos de uso inteligentes y avanzados, algunos de los cuales se emplean ampliamente en la biblioteca de estrategias de petición Alova.

???? Truco 1: Ejecución en serie de un array de promesas

Cuando nos enfrentamos a un escenario en el que una serie de tareas deben ejecutarse secuencialmente, el instinto inicial puede ser utilizar await. Sin embargo, un enfoque alternativo utilizando promesas puede ser más elegante.

const requestAry = [() => api.request1(), () => api.request2(), () => api.request3()];
// Using `await`
for (const requestItem of requestAry) {
 await requestItem();
}
// Using promises for serial execution
const finallyPromise = requestAry.reduce(
 (currentPromise, nextRequest) => currentPromise.then(() => nextRequest()),
 Promise.resolve() // Initial promise for linking promises in the array
);

Este método, que emplea la función then, permite una concatenación concisa y eficiente de promesas, asegurando la ejecución en serie de las tareas. ????

???? Consejo 2: Cambio de estado fuera del ámbito de la nueva promesa

Imagina un escenario en el que varias páginas requieren la recopilación de información del usuario antes de permitir el uso de una función específica. Implementar esto puede ser abordado de manera diferente en función del nivel de habilidad de los desarrolladores frontend.

Para obtener el servidor GRATIS debes de escribir el cupon «LEIFER»


???? FrontEnd elemental:
«Voy a crear una caja modal y copiar-pegar a través de las páginas. Super eficiente!»

????‍???? FrontEnd intermedio:
«Mantener esto es difícil. Encapsulemos el componente por separado e importémoslo donde sea necesario.»

????FrontEnd avanzado:
«¡Por qué no encapsularlo todo! Escribir la llamada al método donde se pueda invocar en cualquier página?»

Para entender el enfoque avanzado, vamos a sumergirnos en un ejemplo de Vue 3:

<! - App.vue →
 <template>
 <! - Modal box component →
 <div class="modal" v-show="visible">
 <div>
 User name: <input v-model="info.name" />
 </div>
 <! - Other information →
 <button @click="handleCancel">Cancel</button>
 <button @click="handleConfirm">Submit</button>
 </div>
 <! - Page components →
</template>
<script setup>
import { provide } from 'vue';
const visible = ref(false);
const info = reactive({
 name: ''
});
let resolveFn, rejectFn;
// Provide the information collection function
provide('getInfoByModal', () => {
 visible.value = true;
 return new Promise((resolve, reject) => {
 // Assign functions to the outside, breaking through the promise scope
 resolveFn = resolve;
 rejectFn = reject;
 });
})
const handleConfirm = () => {
 resolveFn && resolveFn(info);
};
const handleCancel = () => {
 rejectFn && rejectFn(new Error('User has canceled'));
};
</script>

Ahora, getInfoByModal puede obtener sin esfuerzo los datos del usuario llamando a la caja modal:

<template>
 <button @click="handleClick">Fill in the information</button>
</template>
<script setup>
import { inject } from 'vue';
const getInfoByModal = inject('getInfoByModal');
const handleClick = async () => {
 // After the call, the modal box will be displayed. After the user clicks to confirm, the promise fulfills, obtaining the user information.
 const info = await getInfoByModal();
 await api.submitInfo(info);
}
</script>

Esta técnica refleja la encapsulación de componentes comunes en varias bibliotecas de interfaz de usuario. ????

???? Consejo 3: Desvelando el uso alternativo de Async/Await

Muchos están familiarizados con async/await como un medio para recibir el valor de retorno de una función async, sin embargo, pocos reconocen que una función async fundamentalmente devuelve una promesa. Considera estas dos funciones equivalentes:

const fn1 = async () => 1;
const fn2 = () => Promise.resolve(1);
fn1(); // Returns a promise object with a value of 1

Típicamente, await se utiliza con un objeto Promise, esperando a que se resuelva. En consecuencia, await para la función fn1 también es equivalente a lo siguiente:

await fn1();
const promiseInst = fn1();
await promiseInst;

Sin embargo, await guarda un secreto menos conocido. Cuando el valor seguido no es un objeto promesa, envuelve el valor en un objeto promesa. Por lo tanto, el código después de await debe ejecutarse de forma asíncrona. He aquí un ejemplo:

Promise.resolve().then(() => {
 console.log(1);
});
await 2;
console.log(2);
// Output order: 1 2

Esto equivale a:

Promise.resolve().then(() => {
 console.log(1);
});
Promise.resolve().then(() => {
 console.log(2);
});

¡Libera el poder del uso alternativo de await para un mayor control asíncrono! ????

???? Consejo 4: Compromiso para implementar la compartición de peticiones

Enviar peticiones repetidas cuando una petición anterior aún está pendiente puede llevar a un consumo innecesario de recursos.

Una estrategia eficaz es compartir la respuesta de la petición inicial con las siguientes.

request('GET', '/test-api').then(response1 => {
 // …
});
request('GET', '/test-api').then(response2 => {
 // …
});

Sorprendentemente, las dos peticiones anteriores se ejecutan una sola vez, recibiendo ambas la misma respuesta de forma concurrente.

???? Escenarios de uso de la compartición de peticiones:

  1. Cuando se renderizan múltiples componentes internos en una página para obtener datos simultáneamente.
  2. Manejo de escenarios en los que el botón de envío no está desactivado, y el usuario hace clic en él varias veces sucesivamente.
  3. En casos de precarga de datos, navegar a la página de precarga antes de que la precarga se haya completado.

Esta función avanzada también forma parte de las capacidades de Alova. Para implementar la compartición de peticiones, aprovecha la función de caché de promesas, permitiendo que un objeto promise recupere datos a través de múltiples awaits. He aquí una idea de implementación sencilla:

const pendingPromises = {};
function request(type, url, data) {
 const requestKey = JSON.stringify([type, url, data]);
 if (pendingPromises[requestKey]) {
 return pendingPromises[requestKey];
 }
const fetchPromise = fetch(url, {
 method: type,
 data: JSON.stringify(data)
 })
 .then(response => response.json())
 .finally(() => {
 delete pendingPromises[requestKey];
 });
return pendingPromises[requestKey] = fetchPromise;
}

Al optimizar la compartición de peticiones, puedes gestionar los recursos de forma eficiente y mejorar la experiencia del usuario. ????

???? Información adicional: Estado de promesa

Comprender las complejidades de los estados de promesa es crucial. Una vez que una promesa pendiente cambia de estado, permanece inmutable.

En el ejemplo dado, la promesa transita inicialmente al estado cumplido, y reject() posterior no la cambiará de nuevo al estado rechazado. Recuerda, el estado de una promesa se establece una vez que evoluciona. ????

???? Consejo 6: Descifrar los valores de retorno then/catch/finally

Para desmitificar las complejidades, es esencial comprender que estas tres funciones devuelven cada una un nuevo objeto envuelto en una promesa.

El valor encapsulado corresponde al valor de retorno de la función callback. Si se lanza un error dentro de la llamada de retorno, se envuelve una promesa en un estado de rechazo. Vamos a desglosarlo con ejemplos:

???? Función then:

Promise.resolve().then(() => 1); // Returns new Promise(resolve => resolve(1))
Promise.resolve().then(() => Promise.resolve(2)); // Returns new Promise(resolve => resolve(Promise.resolve(2)))
Promise.resolve().then(() => { throw new Error('abc') }); // Returns new Promise(resolve => resolve(Promise.reject(new Error('abc'))))
Promise.reject().then(() => 1, () => 2); // Returns new Promise(resolve => resolve(2))

???? Función catch:

Promise.reject().catch(() => 3); // Returns new Promise(resolve => resolve(3))
Promise.resolve().catch(() => 4); // Returns new Promise(resolve => resolve(promise object calling catch))

???? Función finally:

Promise.resolve().finally(() => {}); // Returns Promise.resolve()
Promise.reject().finally(() => {}); // Returns Promise.reject()

Cuando el valor de retorno de la función finally es una promesa, espera a que la promesa se resuelva antes de devolver el objeto promesa antes de la función finally.

Promise.resolve(5).finally(() => new Promise(res => {
 setTimeout(res, 1000);
})); // Returns a pending Promise, resolves to 5 after 1 second.
Promise.reject(6).finally(() => new Promise(res => {
 setTimeout(res, 1000);
})); // Returns a pending Promise, rejects with 6 after 1 second.

La comprensión de estos valores de retorno desbloquea el potencial para el manejo preciso de promesas. ????

Consejo 7: Distinguir entre la segunda llamada de retorno de then y la llamada de retorno de catch Cuando se produce un error en una petición, se activan tanto la segunda función de callback como la  catchdethende una Promise.

A primera vista, pueden parecer similares, pero hay una diferencia crucial: la primera no puede capturar errores lanzados en la primera función callback dethen, mientras que catch sí puede.

Promise.resolve().then(
 () => {
 throw new Error('Error from success callback');
 },
 () => {
 // This block will not be executed
 }
).catch(reason => {
 console.log(reason.message); // Outputs: "Error from success callback"
});

Como se mencionó anteriormente, la función catch opera sobre la Promise rechazada devuelta por la función then, lo que le permite capturar errores de forma natural.

????️ Principio clave

La función catch es tu red de seguridad, capaz de capturar errores incluso desde la primera función callback.

???? Tip 8 (Final)

Promise implementa el modelo de middleware de cebolla de Koa2
El framework Koa2 introduce el modelo cebolla, proporcionando un enfoque por capas para manejar las peticiones.

Las solicitudes atraviesan a través de capas como pelar una cebolla, asegurando un enfoque sistemático para el procesamiento previo y posterior a la solicitud.

???? Modelo de cebolla en Koa2

  • Las peticiones se mueven capa por capa a través de la pila de middleware.
  • Las capas procesan la petición y contribuyen a la respuesta.
  • Logra una estructura unificada para el manejo de peticiones.

Implementar este modelo con Promises asegura un enfoque robusto y organizado para el manejo de peticiones en tus aplicaciones. ????

Veamos un modelo simple de cebolla koa2:

const app = new Koa();
app.use(async (ctx, next) => {
  console.log('a-start');
  await next();
  console.log('a-end');
});
app.use(async (ctx, next) => {
  console.log('b-start');
  await next();
  console.log('b-end');
});

app.listen(3000);

La salida anterior es a-inicio -> b-inicio -> b-final -> a-final. ¿Cómo se consigue una secuencia de salida tan mágica? Algunas personas no tienen talento y simplemente lo implement

En Grupo MET podemos ayudarte a implementar esta y muchas mas herramienta para optimizar tu trabajo. ¡Contáctanos para saber más!

Contactanos