javascript - promises - Callbacks de promesas devolviendo promesas
promises javascript ajax (4)
En este ejemplo, p2 es una promesa. p3 también es una promesa que se origina en el controlador de cumplimiento de p1. Sin embargo, p2! == p3. En cambio, p2 de alguna manera se resuelve mágicamente a 43 (¿cómo?) Y ese valor se pasa al controlador de cumplimiento de p3. Incluso la oración aquí es confusa.
una versión simplificada de cómo funciona esto (solo pseudocódigo)
function resolve(value){
if(isPromise(value)){
value.then(resolve, reject);
}else{
//dispatch the value to the listener
}
}
Todo esto es bastante más complicado ya que hay que tener cuidado, ya sea que la promesa ya se haya resuelto y algunas cosas más.
Con respecto a estas dos grandes fuentes: NZakas: Devolviendo promesas en Promise Chains y MDN Promises , me gustaría preguntar lo siguiente:
Cada vez que devolvemos un valor de un controlador de cumplimiento de promesa, ¿cómo se transfiere ese valor a la nueva promesa del mismo controlador?
Por ejemplo,
let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
let p2 = new Promise(function(resolve, reject) {
resolve(43);
});
let p3 = p1.then(function(value) {
// first fulfillment handler
console.log(value); // 42
return p2;
});
p3.then(function(value) {
// second fulfillment handler
console.log(value); // 43
});
En este ejemplo,
p2
es una promesa.
p3
también es una promesa que se origina en el controlador de cumplimiento de
p1
.
Sin embargo,
p2 !== p3
.
En cambio,
p2
alguna manera se resuelve mágicamente a
43
(¿cómo?) Y ese valor se pasa al controlador de cumplimiento de
p3
.
Incluso la oración aquí es confusa.
¿Podría explicarme qué está sucediendo exactamente aquí? Estoy totalmente confundido sobre este concepto.
Básicamente,
p3
está
return
una promesa más:
p2
.
Lo que significa que el resultado de
p2
pasará como parámetro a la siguiente devolución de llamada, en este caso se resuelve en
43
.
Cada vez que utiliza la palabra clave
return
, pasa el resultado como parámetro a la siguiente devolución de llamada.
let p3 = p1.then(function(value) {
// first fulfillment handler
console.log(value); // 42
return p2;
});
Tu codigo :
p3.then(function(value) {
// second fulfillment handler
console.log(value); // 43
});
Es igual a:
p1.then(function(resultOfP1) {
// resultOfP1 === 42
return p2; // // Returning a promise ( that might resolve to 43 or fail )
})
.then(function(resultOfP2) {
console.log(resultOfP2) // ''43''
});
Por cierto, me di cuenta de que está utilizando la sintaxis ES6, puede tener una sintaxis más clara utilizando la sintaxis de flecha gruesa:
p1.then(resultOfP1 => p2) // the `return` is implied since it''s a one-liner
.then(resultOfP2 => console.log(resultOfP2));
Digamos que arrojar dentro de la devolución de llamada
then()
rechaza la promesa de resultado con un error, y regresar desde
then()
devolución de llamada
then()
cumple la promesa de resultado con un valor de éxito.
let p2 = p1.then(() => {
throw new Error(''lol'')
})
// p2 was rejected with Error(''lol'')
let p3 = p1.then(() => {
return 42
})
// p3 was fulfilled with 42
Pero a veces, incluso dentro de la continuación, no sabemos si hemos tenido éxito o no. Necesitamos mas tiempo.
return checkCache().then(cachedValue => {
if (cachedValue) {
return cachedValue
}
// I want to do some async work here
})
Sin embargo, si hago un trabajo asíncrono allí, sería demasiado tarde para
return
o
throw
, ¿no?
return checkCache().then(cachedValue => {
if (cachedValue) {
return cachedValue
}
fetchData().then(fetchedValue => {
// Doesn’t make sense: it’s too late to return from outer function by now.
// What do we do?
// return fetchedValue
})
})
Es por eso que Promesas no sería útil si no pudieras resolver otra Promesa .
No significa que, en su ejemplo,
p2
se
convierta en
p3
.
Son objetos separados de Promise.
Sin embargo, al devolver
p2
partir de
then()
que produce
p3
, está diciendo
"Quiero que
p3
resuelva a lo que
p2
resuelva, ya sea que tenga éxito o no".
En cuanto a
cómo
sucede esto, es específico de la implementación.
Internamente, puede pensar en
then()
como la creación de una nueva Promesa.
La implementación podrá cumplirlo o rechazarlo cuando lo desee.
Normalmente, lo cumplirá o rechazará automáticamente cuando regrese:
// Warning: this is just an illustration
// and not a real implementation code.
// For example, it completely ignores
// the second then() argument for clarity,
// and completely ignores the Promises/A+
// requirement that continuations are
// run asynchronously.
then(callback) {
// Save these so we can manipulate
// the returned Promise when we are ready
let resolve, reject
// Imagine this._onFulfilled is an internal
// queue of code to run after current Promise resolves.
this._onFulfilled.push(() => {
let result, error, succeeded
try {
// Call your callback!
result = callback(this._result)
succeeded = true
} catch (err) {
error = err
succeeded = false
}
if (succeeded) {
// If your callback returned a value,
// fulfill the returned Promise to it
resolve(result)
} else {
// If your callback threw an error,
// reject the returned Promise with it
reject(error)
}
})
// then() returns a Promise
return new Promise((_resolve, _reject) => {
resolve = _resolve
reject = _reject
})
}
Nuevamente, esto es en gran medida un seudocódigo, pero muestra la idea detrás de cómo
then()
podría implementar
then()
en las implementaciones de Promise.
Si queremos agregar soporte para resolver una Promesa, solo tenemos que modificar el código para tener una rama especial si la
callback
que pasa
then()
devolvió una Promesa:
if (succeeded) {
// If your callback returned a value,
// resolve the returned Promise to it...
if (typeof result.then === ''function'') {
// ...unless it is a Promise itself,
// in which case we just pass our internal
// resolve and reject to then() of that Promise
result.then(resolve, reject)
} else {
resolve(result)
}
} else {
// If your callback threw an error,
// reject the returned Promise with it
reject(error)
}
})
Permítanme aclarar nuevamente que esta no es una implementación real de Promise y tiene grandes agujeros e incompatibilidades. Sin embargo, debería darle una idea intuitiva de cómo las bibliotecas de Promise implementan la resolución de una Promesa. Después de que se sienta cómodo con la idea, le recomendaría que eche un vistazo a cómo las implementaciones reales de Promise manejan esto .
Trataré de responder la pregunta
"¿por qué
then
devoluciones de llamada pueden devolver las
Promise
mismas"
más canónicas.
Para tomar un ángulo diferente, comparo
Promise
s con un tipo de contenedor menos complejo y confuso:
Array
s.
Una
Promise
es un contenedor para un valor futuro.
Una
Array
es un contenedor para un número arbitrario de valores.
No podemos aplicar funciones normales a los tipos de contenedores:
const sqr = x => x * x;
const xs = [1,2,3];
const p = Promise.resolve(3);
sqr(xs); // fails
sqr(p); // fails
Necesitamos un mecanismo para elevarlos al contexto de un contenedor específico:
xs.map(sqr); // [1,4,9]
p.then(sqr); // Promise {[[PromiseValue]]: 9}
Pero, ¿qué sucede cuando la función proporcionada en sí misma devuelve un contenedor del mismo tipo?
const sqra = x => [x * x];
const sqrp = x => Promise.resolve(x * x);
const xs = [1,2,3];
const p = Promise.resolve(3);
xs.map(sqra); // [[1],[4],[9]]
p.then(sqrp); // Promise {[[PromiseValue]]: 9}
sqra
actúa como se esperaba.
Simplemente devuelve un contenedor anidado con los valores correctos.
Sin embargo, esto obviamente no es muy útil.
Pero, ¿cómo se puede interpretar el resultado de
sqrp
?
Si seguimos nuestra propia lógica, tenía que ser algo así como
Promise {[[PromiseValue]]: Promise {[[PromiseValue]]: 9}}
, pero no lo es.
Entonces, ¿qué magia está pasando aquí?
Para reconstruir el mecanismo simplemente necesitamos adaptar un poco nuestro método de
map
:
const flatten = f => x => f(x)[0];
const sqra = x => [x * x];
const sqrp = x => Promise.resolve(x * x);
const xs = [1,2,3];
xs.map(flatten(sqra))
flatten
solo toma una función y un valor, aplica la función al valor y desenvuelve el resultado, por lo que reduce una estructura de matriz anidada en un nivel.
En pocas palabras, en el contexto de
Promise
s es equivalente a
map
combinado con
flatten
en el contexto de
Array
s.
Este comportamiento es extremadamente importante.
Podemos aplicar no solo funciones normales a una
Promise
sino también funciones que en sí mismas devuelven una
Promise
.
De hecho, este es el dominio de la programación funcional.
Una
Promise
es una implementación específica de una
mónada
,
then
es
bind
/
chain
y una función que devuelve una
Promise
es una función monádica.
Cuando comprendes la API de
Promise
, básicamente entiendes a todas las mónadas.