javascript - definicion - title css
¿Cómo accedo a resultados de promesas anteriores en una cadena.then()? (17)
Anidamiento (y) cierres
El uso de cierres para mantener el alcance de las variables (en nuestro caso, los parámetros de la función de devolución de llamada exitosa) es la solución natural de JavaScript.
Con las promesas, podemos
anidar y aplanar
.then()
devoluciones de llamada
.then()
: son semánticamente equivalentes, excepto por el alcance del interno.
function getExample() {
return promiseA(…).then(function(resultA) {
// some processing
return promiseB(…).then(function(resultB) {
// more processing
return // something using both resultA and resultB;
});
});
}
Por supuesto, esto es construir una pirámide de sangría.
Si la sangría se está volviendo demasiado grande, aún puede aplicar las herramientas antiguas para contrarrestar la
pirámide de la fatalidad
: modularizar, usar funciones con nombre adicionales y aplanar la cadena de promesa tan pronto como ya no necesite una variable.
En teoría, siempre puede evitar más de dos niveles de anidamiento (haciendo explícitos todos los cierres), en la práctica, use tantos como sea razonable.
function getExample() {
// preprocessing
return promiseA(…).then(makeAhandler(…));
}
function makeAhandler(…)
return function(resultA) {
// some processing
return promiseB(…).then(makeBhandler(resultA, …));
};
}
function makeBhandler(resultA, …) {
return function(resultB) {
// more processing
return // anything that uses the variables in scope
};
}
También puede usar funciones auxiliares para este tipo de
aplicación parcial
, como
_.partial
de
Underscore
/
lodash
o el
.bind()
nativo
.bind()
, para disminuir aún más la sangría:
function getExample() {
// preprocessing
return promiseA(…).then(handlerA);
}
function handlerA(resultA) {
// some processing
return promiseB(…).then(handlerB.bind(null, resultA));
}
function handlerB(resultA, resultB) {
// more processing
return // anything that uses resultA and resultB
}
He reestructurado mi código a
promises
, y he creado una maravillosa
cadena de promesa
larga y
plana
, que consta de múltiples devoluciones de llamada
.then()
.
Al final quiero devolver algún valor compuesto, y necesito acceder a múltiples
resultados de promesa intermedios
.
Sin embargo, los valores de resolución del medio de la secuencia no están dentro del alcance en la última devolución de llamada, ¿cómo puedo acceder a ellos?
function getExample() {
return promiseA(…).then(function(resultA) {
// Some processing
return promiseB(…);
}).then(function(resultB) {
// More processing
return // How do I gain access to resultA here?
});
}
Armonía ECMAScript
Por supuesto, este problema también fue reconocido por los diseñadores de idiomas. Hicieron mucho trabajo y la propuesta de funciones asincrónicas finalmente se convirtió en
ECMAScript 8
Ya no necesita una sola función de invocación o devolución de llamada, ya que en una función asincrónica (que devuelve una promesa cuando se le llama) simplemente puede esperar a que las promesas se resuelvan directamente. También presenta estructuras de control arbitrarias como condiciones, bucles y cláusulas try-catch-but, pero por conveniencia no las necesitamos aquí:
async function getExample() {
var resultA = await promiseA(…);
// some processing
var resultB = await promiseB(…);
// more processing
return // something using both resultA and resultB
}
ECMAScript 6
Mientras esperábamos ES8, ya usamos un tipo de sintaxis muy similar.
ES6 viene con
funciones de generador
, que permiten separar la ejecución en partes en palabras clave de
yield
colocadas arbitrariamente.
Esos segmentos se pueden ejecutar uno tras otro, de forma independiente, incluso de forma asincrónica, y eso es exactamente lo que hacemos cuando queremos esperar una resolución prometedora antes de ejecutar el siguiente paso.
Hay bibliotecas dedicadas (como co o task.js ), pero también muchas bibliotecas prometedoras tienen funciones auxiliares ( Q , Bluebird , when , ...) que realizan esta ejecución asíncrona paso a paso cuando les das una función de generador que rinde promesas.
var getExample = Promise.coroutine(function* () {
// ^^^^^^^^^^^^^^^^^ Bluebird syntax
var resultA = yield promiseA(…);
// some processing
var resultB = yield promiseB(…);
// more processing
return // something using both resultA and resultB
});
Esto funcionó en Node.js desde la versión 4.0, también algunos navegadores (o sus ediciones dev) admitieron la sintaxis del generador relativamente temprano.
ECMAScript 5
Sin embargo, si desea / necesita ser compatible con versiones anteriores, no puede usarlos sin un transpilador. Tanto las funciones de generador como las funciones asíncronas son compatibles con las herramientas actuales; consulte, por ejemplo, la documentación de Babel sobre generators y funciones asíncronas .
Y luego, también hay muchos otros
lenguajes de compilación a JS
que están dedicados a facilitar la programación asincrónica.
Por lo general, usan una sintaxis similar a la
await
(por ejemplo,
Iced CoffeeScript
), pero también hay otras que presentan una anotación
do
-like de Haskell (por ejemplo,
PureScript
,
PureScript
,
PureScript
o
LispyScript
).
Estado contextual mutable
La solución trivial (pero poco elegante y bastante propensa a errores) es usar solo variables de mayor alcance (a las que todas las devoluciones de llamada en la cadena tienen acceso) y escribirles valores de resultados cuando las obtenga:
function getExample() {
var resultA;
return promiseA(…).then(function(_resultA) {
resultA = _resultA;
// some processing
return promiseB(…);
}).then(function(resultB) {
// more processing
return // something using both resultA and resultB
});
}
En lugar de muchas variables, también se puede usar un objeto (inicialmente vacío), en el que los resultados se almacenan como propiedades creadas dinámicamente.
Esta solución tiene varios inconvenientes:
- El estado mutable es feo y las variables globales son malas .
- Este patrón no funciona a través de los límites de la función, modularizar las funciones es más difícil ya que sus declaraciones no deben abandonar el alcance compartido
- El alcance de las variables no impide acceder a ellas antes de que se inicialicen. Esto es especialmente probable para construcciones prometedoras complejas (bucles, ramificaciones, excitaciones) donde pueden ocurrir condiciones de carrera. La aprobación explícita del estado, un diseño declarativo que promete alentar, obliga a un estilo de codificación más limpio que puede evitar esto.
- Uno debe elegir el alcance de esas variables compartidas correctamente. Debe ser local a la función ejecutada para evitar condiciones de carrera entre invocaciones paralelas múltiples, como sería el caso si, por ejemplo, el estado se almacenara en una instancia.
La biblioteca Bluebird fomenta el uso de un objeto que se pasa, utilizando
su método
bind()
para asignar un objeto de contexto a una cadena de promesa.
Será accesible desde cada función de devolución de llamada a través de
this
palabra clave que de
otro modo no se podría usar.
Si bien las propiedades de los objetos son más propensas a errores tipográficos no detectados que a las variables, el patrón es bastante inteligente:
function getExample() {
return promiseA(…)
.bind({}) // Bluebird only!
.then(function(resultA) {
this.resultA = resultA;
// some processing
return promiseB(…);
}).then(function(resultB) {
// more processing
return // something using both this.resultA and resultB
}).bind(); // don''t forget to unbind the object if you don''t want the
// caller to access it
}
Este enfoque se puede simular fácilmente en bibliotecas prometedoras que no admiten .bind (aunque de una manera algo más detallada y no se pueden usar en una expresión):
function getExample() {
var ctx = {};
return promiseA(…)
.then(function(resultA) {
this.resultA = resultA;
// some processing
return promiseB(…);
}.bind(ctx)).then(function(resultB) {
// more processing
return // something using both this.resultA and resultB
}.bind(ctx));
}
Inspección sincrónica
Asignando promesas para valores necesarios más tarde a variables y luego obteniendo su valor mediante inspección sincrónica.
El ejemplo utiliza el método
.value()
bluebird, pero muchas bibliotecas proporcionan un método similar.
function getExample() {
var a = promiseA(…);
return a.then(function() {
// some processing
return promiseB(…);
}).then(function(resultB) {
// a is guaranteed to be fulfilled here so we can just retrieve its
// value synchronously
var aValue = a.value();
});
}
Esto se puede usar para tantos valores como desee:
function getExample() {
var a = promiseA(…);
var b = a.then(function() {
return promiseB(…)
});
var c = b.then(function() {
return promiseC(…);
});
var d = c.then(function() {
return promiseD(…);
});
return d.then(function() {
return a.value() + b.value() + c.value() + d.value();
});
}
Paso explícito
Similar a anidar las devoluciones de llamada, esta técnica se basa en cierres. Sin embargo, la cadena se mantiene plana: en lugar de pasar solo el último resultado, se pasa algún objeto de estado por cada paso. Estos objetos de estado acumulan los resultados de las acciones anteriores, transmitiendo todos los valores que se necesitarán más tarde más el resultado de la tarea actual.
function getExample() {
return promiseA(…).then(function(resultA) {
// some processing
return promiseB(…).then(b => [resultA, b]); // function(b) { return [resultA, b] }
}).then(function([resultA, resultB]) {
// more processing
return // something using both resultA and resultB
});
}
Aquí, esa pequeña flecha
b => [resultA, b]
es la función que se cierra sobre el
resultA
, y pasa una matriz de ambos resultados al siguiente paso.
Que utiliza la sintaxis de desestructuración de parámetros para dividirla en variables individuales nuevamente.
Antes de que la desestructuración estuviera disponible con ES6, muchas bibliotecas prometedoras proporcionaron un ingenioso método auxiliar llamado
.spread()
(
Q
,
Bluebird
,
when
, ...).
Se necesita una función con múltiples parámetros, uno para cada elemento de la matriz, para usarse como
.spread(function(resultA, resultB) { …
.
Por supuesto, ese cierre necesario aquí puede simplificarse aún más mediante algunas funciones auxiliares, por ejemplo,
function addTo(x) {
// imagine complex `arguments` fiddling or anything that helps usability
// but you get the idea with this simple one:
return res => [x, res];
}
…
return promiseB(…).then(addTo(resultA));
Alternativamente, puede emplear
Promise.all
para producir la promesa para la matriz:
function getExample() {
return promiseA(…).then(function(resultA) {
// some processing
return Promise.all([resultA, promiseB(…)]); // resultA will implicitly be wrapped
// as if passed to Promise.resolve()
}).then(function([resultA, resultB]) {
// more processing
return // something using both resultA and resultB
});
}
Y no solo puede usar matrices, sino también objetos complejos arbitrariamente.
Por ejemplo, con
_.extend
u
Object.assign
en una función auxiliar diferente:
function augment(obj, name) {
return function (res) { var r = Object.assign({}, obj); r[name] = res; return r; };
}
function getExample() {
return promiseA(…).then(function(resultA) {
// some processing
return promiseB(…).then(augment({resultA}, "resultB"));
}).then(function(obj) {
// more processing
return // something using both obj.resultA and obj.resultB
});
}
Si bien este patrón garantiza una cadena plana y los objetos de estado explícito pueden mejorar la claridad, se volverá tedioso para una cadena larga. Especialmente cuando necesita el estado solo esporádicamente, todavía tiene que pasarlo por cada paso. Con esta interfaz fija, las devoluciones de llamada individuales en la cadena están bastante unidas e inflexibles para cambiar. Hace que la factorización de pasos individuales sea más difícil, y las devoluciones de llamada no se pueden suministrar directamente desde otros módulos; siempre deben incluirse en un código repetitivo que se preocupe por el estado. Las funciones auxiliares abstractas como las anteriores pueden aliviar un poco el dolor, pero siempre estarán presentes.
Romper la cadena
Cuando necesite acceder a los valores intermedios en su cadena, debe dividir su cadena en esas piezas individuales que necesita. En lugar de adjuntar una devolución de llamada y de alguna manera tratar de usar su parámetro varias veces, adjunte varias devoluciones de llamada a la misma promesa, siempre que necesite el valor del resultado. ¡No olvide que una promesa solo representa (representa) un valor futuro ! Además de derivar una promesa de la otra en una cadena lineal, use los combinadores de promesa que le proporciona su biblioteca para generar el valor del resultado.
Esto dará como resultado un flujo de control muy sencillo, una composición clara de funcionalidades y, por lo tanto, una fácil modularización.
function getExample() {
var a = promiseA(…);
var b = a.then(function(resultA) {
// some processing
return promiseB(…);
});
return Promise.all([a, b]).then(function([resultA, resultB]) {
// more processing
return // something using both resultA and resultB
});
}
En lugar de la desestructuración de parámetros en la devolución de llamada después de
Promise.all
que solo estuvo disponible con ES6, en ES5 la llamada
then
sería reemplazada por un ingenioso método auxiliar que fue proporcionado por muchas bibliotecas prometedoras (
Q
,
Bluebird
,
when
, ...):.
.spread(function(resultA, resultB) { …
.
Bluebird también presenta una
función de
join
dedicada para reemplazar esa combinación
Promise.all
+
spread
con una construcción más simple (y más eficiente):
…
return Promise.join(a, b, function(resultA, resultB) { … });
Un giro menos duro en el "estado contextual mutable"
El uso de un objeto de ámbito local para recopilar los resultados intermedios en una cadena de promesa es un enfoque razonable de la pregunta que planteó. Considere el siguiente fragmento:
function getExample(){
//locally scoped
const results = {};
return promiseA(paramsA).then(function(resultA){
results.a = resultA;
return promiseB(paramsB);
}).then(function(resultB){
results.b = resultB;
return promiseC(paramsC);
}).then(function(resultC){
//Resolve with composite of all promises
return Promise.resolve(results.a + results.b + resultC);
}).catch(function(error){
return Promise.reject(error);
});
}
- Las variables globales son malas, por lo que esta solución utiliza una variable de ámbito local que no causa ningún daño. Solo es accesible dentro de la función.
- El estado mutable es feo, pero esto no muta de una manera fea. El estado mutable feo tradicionalmente se refiere a la modificación del estado de los argumentos de la función o las variables globales, pero este enfoque simplemente modifica el estado de una variable de alcance local que existe con el único propósito de agregar resultados prometedores ... una variable que morirá de muerte simple Una vez que la promesa se resuelva.
- No se impide que las promesas intermedias accedan al estado del objeto de resultados, pero esto no introduce algún escenario aterrador en el que una de las promesas de la cadena se vuelva deshonesta y sabotee sus resultados. La responsabilidad de establecer los valores en cada paso de la promesa se limita a esta función y el resultado general será correcto o incorrecto ... no será un error que surgirá años después en la producción (a menos que tenga la intención de hacerlo). !)
- Esto no introduce un escenario de condición de carrera que surgiría de la invocación paralela porque se crea una nueva instancia de la variable de resultados para cada invocación de la función getExample.
Creo que puedes usar hash de RSVP.
Algo así como a continuación:
const mainPromise = () => {
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(''first promise is completed'');
resolve({data: ''123''});
}, 2000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(''second promise is completed'');
resolve({data: ''456''});
}, 2000);
});
return new RSVP.hash({
prom1: promise1,
prom2: promise2
});
};
mainPromise()
.then(data => {
console.log(data.prom1);
console.log(data.prom2);
});
Cuando use bluebird, puede usar el método
.bind
para compartir variables en la cadena de promesa:
somethingAsync().bind({})
.spread(function (aValue, bValue) {
this.aValue = aValue;
this.bValue = bValue;
return somethingElseAsync(aValue, bValue);
})
.then(function (cValue) {
return this.aValue + this.bValue + cValue;
});
por favor revise este enlace para más información:
El nodo 7.4 ahora admite llamadas asíncronas / en espera con el indicador de armonía.
Prueba esto:
async function getExample(){
let response = await returnPromise();
let response2 = await returnPromise2();
console.log(response, response2)
}
getExample()
y ejecuta el archivo con:
node --harmony-async-await getExample.js
Tan simple como puede ser!
En estos días, también he tenido algunas preguntas como tú. Por fin, encuentro una buena solución con la pregunta, es simple y buena de leer. Espero que esto pueda ayudarte.
De acuerdo a how-to-chain-javascript-promises
ok, veamos el código:
const firstPromise = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(''first promise is completed'');
resolve({data: ''123''});
}, 2000);
});
};
const secondPromise = (someStuff) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(''second promise is completed'');
resolve({newData: `${someStuff.data} some more data`});
}, 2000);
});
};
const thirdPromise = (someStuff) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(''third promise is completed'');
resolve({result: someStuff});
}, 2000);
});
};
firstPromise()
.then(secondPromise)
.then(thirdPromise)
.then(data => {
console.log(data);
});
Lo que aprendo sobre las promesas es usarlo solo como valores de retorno, evite hacer referencia a ellos si es posible. La sintaxis async / await es particularmente práctica para eso. Hoy todos los últimos navegadores y nodos lo admiten: https://caniuse.com/#feat=async-functions , es un comportamiento simple y el código es como leer código síncrono, olvidarse de las devoluciones de llamada ...
En los casos en que necesito hacer referencia a una promesa es cuando la creación y la resolución suceden en lugares independientes / no relacionados. Por lo tanto, en lugar de una asociación artificial y probablemente un oyente de eventos solo para resolver la promesa "distante", prefiero exponer la promesa como diferida, que el siguiente código lo implementa en es5 válido
/** * Promise like object that allows to resolve it promise from outside code. Example: * ``` class Api { fooReady = new Deferred<Data>() private knower() { inOtherMoment(data=>{ this.fooReady.resolve(data) }) } } ``` */ var Deferred = /** @class */ (function () { function Deferred(callback) { var instance = this; this.resolve = null; this.reject = null; this.status = ''pending''; this.promise = new Promise(function (resolve, reject) { instance.resolve = function () { this.status = ''resolved''; resolve.apply(this, arguments); }; instance.reject = function () { this.status = ''rejected''; reject.apply(this, arguments); }; }); if (typeof callback === ''function'') { callback.call(this, this.resolve, this.reject); } } Deferred.prototype.then = function (resolve) { return this.promise.then(resolve); }; Deferred.prototype.catch = function (r) { return this.promise.catch(r); }; return Deferred; }());
Transpilado de un proyecto mecanografiado mío:
Para casos más complejos, a menudo utilizo estas pequeñas utilidades de promesa sin dependencias probadas y escritas. p-map ha sido útil varias veces. Creo que cubrió la mayoría de los casos de uso:
https://github.com/sindresorhus?utf8=%E2%9C%93&tab=repositories&q=promise&type=source&language=
No voy a usar este patrón en mi propio código ya que no soy un gran fanático del uso de variables globales. Sin embargo, en caso de apuro funcionará.
El usuario es un modelo de Mangosta prometido.
var globalVar = '''';
User.findAsync({}).then(function(users){
globalVar = users;
}).then(function(){
console.log(globalVar);
});
Otra respuesta, usando
babel-node
versión <6
Usando
async - await
npm install -g [email protected]
example.js:
async function getExample(){
let response = await returnPromise();
let response2 = await returnPromise2();
console.log(response, response2)
}
getExample()
Luego, ejecute
babel-node example.js
y listo.
Otra respuesta, usando el ejecutor secuencial nsynjs :
function getExample(){
var response1 = returnPromise1().data;
// promise1 is resolved at this point, ''.data'' has the result from resolve(result)
var response2 = returnPromise2().data;
// promise2 is resolved at this point, ''.data'' has the result from resolve(result)
console.log(response, response2);
}
nynjs.run(getExample,{},function(){
console.log(''all done'');
})
Actualización: ejemplo de trabajo agregado
function synchronousCode() {
var urls=[
"https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js",
"https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js",
"https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"
];
for(var i=0; i<urls.length; i++) {
var len=window.fetch(urls[i]).data.text().data.length;
// ^ ^
// | +- 2-nd promise result
// | assigned to ''data''
// |
// +-- 1-st promise result assigned to ''data''
//
console.log(''URL #''+i+'' : ''+urls[i]+", length: "+len);
}
}
nsynjs.run(synchronousCode,{},function(){
console.log(''all done'');
})
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
Solución:
Puede poner valores intermedios en el alcance en cualquier función posterior ''entonces'' explícitamente, utilizando ''bind''. Es una buena solución que no requiere cambiar la forma en que funcionan las Promesas, y solo requiere una o dos líneas de código para propagar los valores al igual que los errores ya se propagan.
Aquí hay un ejemplo completo:
// Get info asynchronously from a server
function pGetServerInfo()
{
// then value: "server info"
} // pGetServerInfo
// Write into a file asynchronously
function pWriteFile(path,string)
{
// no then value
} // pWriteFile
// The heart of the solution: Write formatted info into a log file asynchronously,
// using the pGetServerInfo and pWriteFile operations
function pLogInfo(localInfo)
{
var scope={localInfo:localInfo}; // Create an explicit scope object
var thenFunc=p2.bind(scope); // Create a temporary function with this scope
return (pGetServerInfo().then(thenFunc)); // Do the next ''then'' in the chain
} // pLogInfo
// Scope of this ''then'' function is {localInfo:localInfo}
function p2(serverInfo)
{
// Do the final ''then'' in the chain: Writes "local info, server info"
return pWriteFile(''log'',this.localInfo+'',''+serverInfo);
} // p2
Esta solución se puede invocar de la siguiente manera:
pLogInfo("local info").then().catch(err);
(Nota: se ha probado una versión más compleja y completa de esta solución, pero no esta versión de ejemplo, por lo que podría tener un error).
function getExample() {
var retA, retB;
return promiseA(…).then(function(resultA) {
retA = resultA;
// Some processing
return promiseB(…);
}).then(function(resultB) {
// More processing
//retA is value of promiseA
return // How do I gain access to resultA here?
});
}
manera fácil: D