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)

Fuente

Plataforma de cursos gratis sobre programación