Una experiencia de entrevista que me entristeció mucho.
Esta es una experiencia de entrevista que le ocurrió a mi amigo no hace mucho. El entrevistador quería que implementara la función Promise.all
Por desgracia, mi amigo no se desenvolvió bien en el momento y no pudo responder a la pregunta.
Después de la entrevista, el entrevistador le dijo: "Tu base de JavaScript no es lo suficientemente sólida, y tienes conocimientos poco adecuados de muchos principios de JavaScript".
La verdad es que me desconcierta este comportamiento del entrevistador. ¿La incapacidad para aplicar Promise.all
significa que no se dominan los fundamentos? Es extraño, ¿no crees?
En lo que sigue, intento desmitificar Promise.all
y otros métodos Promise
importantes. Así que empecemos.
Promise.all
De MDN:
"El método Promise.all()
toma un iterable de promesas como entrada, y devuelve una única promesa que resuelve un array de los resultados de las promesas de entrada.
Esta promesa devuelta se cumplirá cuando todas las promesas de entrada se hayan cumplido, o si el iterable de entrada no contiene promesas.
Rechazará inmediatamente si alguna de las promesas de entrada rechaza o las no-promesas lanzan un error, y rechazará con este primer mensaje de rechazo/error".
Ejemplo
Vamos a escribir algunos ejemplos para repasar cómo utilizar Promise.all
const p1 = Promise.resolve(1)
const p2 = new Promise((resolve) => {
setTimeout(() => resolve(2), 1000)
})
const p3 = new Promise((resolve) => {
setTimeout(() => resolve(3), 3000)
})
const p4 = Promise.reject('err4')
const p5 = Promise.reject('err5')
// 1. All Promises Succeed
const p11 = Promise.all([ p1, p2, p3 ])
.then(console.log) // [ 1, 2, 3 ]
.catch(console.log)
// 2. There is a Promise that fails
const p12 = Promise.all([ p1, p2, p4 ])
.then(console.log)
.catch(console.log) // err4
// 3. There are two Promise failures, you can see the final output is err4, the first failed return value
const p13 = Promise.all([ p1, p4, p5 ])
.then(console.log)
.catch(console.log) // err4
A estas alturas, creo que ya sabes cómo implementarlo, es muy fácil, ¿verdad?
Código
Promise.myAll = (promises) => {
return new Promise((rs, rj) => {
// counter
let count = 0
// to store the results
let result = []
const len = promises.length
if (len === 0) {
return rs([])
}
promises.forEach((p, i) => {
// Note: Some array items may not be Promise and need to be converted to Promise manually.
Promise.resolve(p).then((res) => {
count += 1
// Collect the return value of each Promise
result[ i ] = res
// When all Promises are successful, set the returned Promise result to result
if (count === len) {
rs(result)
}
// As long as one Promise fails, the result fails
}).catch(rj)
})
})
}
Hagamos una prueba
const p1 = Promise.resolve(1)
const p2 = new Promise((resolve) => {
setTimeout(() => resolve(2), 1000)
})
const p3 = new Promise((resolve) => {
setTimeout(() => resolve(3), 3000)
})
const p4 = Promise.reject('err4')
const p5 = Promise.reject('err5')
// 1. All Promises Succeed
const p11 = Promise.myAll([ p1, p2, p3 ])
.then(console.log) // [ 1, 2, 3 ]
.catch(console.log)
// 2. There is a Promise that fails
const p12 = Promise.myAll([ p1, p2, p4 ])
.then(console.log)
.catch(console.log) // err4
// 3. There are two Promise failures, you can see the final output is err4, the first failed return value
const p13 = Promise.myAll([ p1, p4, p5 ])
.then(console.log)
.catch(console.log) // err4
Puedes ver que Promise.myAll
hace lo mismo que Promise.all.
Promise.resolve
Amigo, gracias por leer y me gustaría invitarte a seguir leyendo. Vamos a implementar Promise.resolve
de nuevo.
De MDN:
"El método Promise.resolve()
"resuelve" un valor dado a una Promise. Si el valor es una promesa, esa promesa se devuelve; si el valor es un thenable
, Promise.resolve()
llamará al método then()
con dos callbacks
que preparó; en caso contrario, la promesa devuelta se cumplirá con el valor."
Ejemplo:
// 1. Non-Promise object, non-thenable object
Promise.resolve(1).then(console.log) // 1
// 2. Promise object success status
const p2 = new Promise((resolve) => resolve(2))
Promise.resolve(p2).then(console.log) // 2
// 3. Promise object failure status
const p3 = new Promise((_, reject) => reject('err3'))
Promise.resolve(p3).catch(console.error) // err3
// 4. thenable object
const p4 = {
then (resolve) {
setTimeout(() => resolve(4), 1000)
}
}
Promise.resolve(p4).then(console.log) // 4
// 5. nothing is delivered
Promise.resolve().then(console.log) // undefined
Código
No tenía ni idea de que Promise.resolve
fuera tan fácil.
Promise.myResolve = function (value) {
// If value is a Promise, just return it directly
if (value && typeof value === 'object' && (value instanceof Promise)) {
return value
}
// Otherwise, all other cases are wrapped again by Promise
return new Promise((resolve) => {
resolve(value)
})
}
Vamos a probar
// 1. Non-Promise object, non-thenable object
Promise.myResolve(1).then(console.log) // 1
// 2. Promise object success status
const p2 = new Promise((resolve) => resolve(2))
Promise.myResolve(p2).then(console.log) // 2
// 3. Promise object failure status
const p3 = new Promise((_, reject) => reject('err3'))
Promise.myResolve(p3).catch(console.error) // err3
// 4. thenable object
const p4 = {
then (resolve) {
setTimeout(() => resolve(4), 1000)
}
}
Promise.myResolve(p4).then(console.log) // 4
// 5. nothing is delivered
Promise.myResolve().then(console.log) // undefined
Promise.reject
¿Qué tal implementar Promise.reject
?
De MDN:
"El método Promise.reject()
devuelve un objeto Promise rechazado con una razón dada".
Ejemplo:
Promise.reject(new Error('fail'))
.then(() => console.log('Resolved'),
(err) => console.log('Rejected', err))
// Output the following
// Rejected Error: fail
// at <anonymous>:2:16
Código
Implementar un Promise.reject
es muy sencillo. Todo lo que tenemos que hacer es asegurarnos de que devolvemos una nueva Promise y establecer el estado resultante a reject.
Promise.myReject = function (value) {
return new Promise((_, reject) => {
reject(value)
})
}
Probemos
Promise.myReject(new Error('fail'))
.then(() => console.log('Resolved'),
(err) => console.log('Rejected', err))
// Rejected Error: fail
// at <anonymous>:9:18
Promesa.carrera
De MDN:
"El método Promise.race()
devuelve una promesa que se cumple o rechaza en cuanto una de las promesas de un iterable se cumple o rechaza, con el valor o razón de esa promesa."
Ejemplo
const p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 1)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 2)
})
Promise.race([p1, p2]).then((value) => {
console.log(value) // 2
})
Promise.race([p1, p2, 3]).then((value) => {
console.log(value) // 3
})
Código
Debes saber cómo implementarlo. Mientras sepas qué instancia cambió primero, entonces Promise.race
sigue este resultado, y puedes escribir el siguiente código:
Promise.myRace = (promises) => {
return new Promise((rs, rj) => {
promises.forEach((p) => {
// 1. Wrap p once to prevent it from not being a Promise object
// 2. Any Promise in promises changes state first, the Promise will follow its result
Promise.resolve(p).then(rs).catch(rj)
})
})
}
Probemos
const p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 1)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 2)
})
Promise.myRace([p1, p2]).then((value) => {
console.log(value) // 2
})
Promise.myRace([p1, p2, 3]).then((value) => {
console.log(value) // 3
})
Promise.allSettled
Esta es la última API de la Promise que implementaremos, y en realidad la utilizamos en muy pocas ocasiones.
Lo siguiente es de MDN:
"El método Promise.allSettled()
devuelve una promesa que se cumple después de que todas las promesas dadas se hayan cumplido o rechazado, con un array de objetos que describen cada uno el resultado de cada promesa.
Se utiliza normalmente cuando se tienen múltiples tareas asíncronas que no dependen unas de otras para completarse con éxito, o siempre se desea conocer el resultado de cada promesa.
En comparación, la Promise devuelta por Promise.all()
puede ser más apropiada si las tareas dependen unas de otras/si te gustaría rechazar inmediatamente al rechazar cualquiera de ellas."
Ejemplo
const p1 = Promise.resolve(1)
const p2 = new Promise((resolve) => {
setTimeout(() => resolve(2), 1000)
})
const p3 = new Promise((resolve) => {
setTimeout(() => resolve(3), 3000)
})
const p4 = Promise.reject('err4')
const p5 = Promise.reject('err5')
// 1. All Promises Succeed
const p11 = Promise.allSettled([ p1, p2, p3 ])
.then((res) => console.log(JSON.stringify(res, null, 2)))
/*
[
{
"status": "fulfilled",
"value": 1
},
{
"status": "fulfilled",
"value": 2
},
{
"status": "fulfilled",
"value": 3
}
]
*/
// 2. There is a Promise that fails
const p12 = Promise.allSettled([ p1, p2, p4 ])
.then((res) => console.log(JSON.stringify(res, null, 2)))
/*
[
{
"status": "fulfilled",
"value": 1
},
{
"status": "fulfilled",
"value": 2
},
{
"status": "rejected",
"reason": "err4"
}
]
*/
// 3. There are two Promises that fail
const p13 = Promise.allSettled([ p1, p4, p5 ])
.then((res) => console.log(JSON.stringify(res, null, 2)))
/*
[
{
"status": "fulfilled",
"value": 1
},
{
"status": "rejected",
"reason": "err4"
},
{
"status": "rejected",
"reason": "err5"
}
]
*/
Código
Por favor, espere, mi querido amigo, este artículo está a punto de terminar.
A través del ejemplo anterior, resumimos sus reglas.
Tanto si la Promesa es totalmente exitosa como si es parcialmente fallida, finalmente entrará en el callback .then
de Promise.allSettled
En el valor de retorno final, tanto los elementos exitosos como los fallidos tienen el atributo status, el valor se cumple cuando es exitoso, y se rechaza cuando falla
En el valor de retorno final, el éxito contiene el atributo value, mientras que el fracaso es el atributo reason.
Promise.myAllSettled = (promises) => {
return new Promise((rs, rj) => {
let count = 0
let result = []
const len = promises.length
// If the array is empty, return empty data directly
if (len === 0) {
return rs([])
}
promises.forEach((p, i) => {
Promise.resolve(p).then((res) => {
count += 1
// set success attribute
result[ i ] = {
status: 'fulfilled',
value: res
}
if (count === len) {
rs(result)
}
}).catch((err) => {
count += 1
// set failure property
result[i] = {
status: 'rejected',
reason: err
}
if (count === len) {
rs(result)
}
})
})
})
}
Vamos a probar:
const p1 = Promise.resolve(1)
const p2 = new Promise((resolve) => {
setTimeout(() => resolve(2), 1000)
})
const p3 = new Promise((resolve) => {
setTimeout(() => resolve(3), 3000)
})
const p4 = Promise.reject('err4')
const p5 = Promise.reject('err5')
// 1. All Promises Succeed
const p11 = Promise.myAllSettled([ p1, p2, p3 ])
.then((res) => console.log(JSON.stringify(res, null, 2)))
/*
[
{
"status": "fulfilled",
"value": 1
},
{
"status": "fulfilled",
"value": 2
},
{
"status": "fulfilled",
"value": 3
}
]
*/
// 2. There is a Promise that fails
const p12 = Promise.myAllSettled([ p1, p2, p4 ])
.then((res) => console.log(JSON.stringify(res, null, 2)))
/*
[
{
"status": "fulfilled",
"value": 1
},
{
"status": "fulfilled",
"value": 2
},
{
"status": "rejected",
"reason": "err4"
}
]
*/
// 3. There are two Promises that fail
const p13 = Promise.myAllSettled([ p1, p4, p5 ])
.then((res) => console.log(JSON.stringify(res, null, 2)))
/*
[
{
"status": "fulfilled",
"value": 1
},
{
"status": "rejected",
"reason": "err4"
},
{
"status": "rejected",
"reason": "err5"
}
]
*/
Gracias por llegar hasta el final de este blog quiero recordarte que todo esto es gratis y posible gracias a que tu compartes. Un fuerte abrazo y recuerda que el conocimiento es poder.
Invertir en conocimientos produce siempre los mejores beneficios. (Benjamín Franklin)