He reestructurado mi código según las promesas y he construido una maravillosa cadena de promesas planas , que consta de varias .then()
devoluciones de llamada. Al final, quiero devolver algún valor compuesto y necesito acceder a múltiples resultados de promesas intermedias . Sin embargo, los valores de resolución de la mitad de la secuencia no están dentro del alcance en la última devolución de llamada, ¿cómo accedo 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?
});
}
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 intentar usar su parámetro varias veces, adjunte varias devoluciones de llamada a la misma promesa, siempre que necesite el valor de resultado. ¡No lo olvides, una promesa solo representa (representa) un valor futuro ! Además de derivar una promesa de la otra en una cadena lineal, utilice los combinadores de promesas 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 parámetro en la devolución de llamada después de Promise.all
que sólo estaba disponible con ES6, ES5 en la then
llamada sería reemplazado por un método de ayuda ingenioso que fue proporcionado por muchas bibliotecas Promise ( Q , Bluebird , cuando , ...): .spread(function(resultA, resultB) { …
.
Bluebird también cuenta con un dedicado join
función de reemplazar ese Promise.all
+ spread
combinación con una construcción más simple (y más económica):
…
return Promise.join(a, b, function(resultA, resultB) { … });
Por supuesto, este problema también fue reconocido por los diseñadores de lenguajes. Hicieron mucho trabajo y la propuesta de funciones asíncronas finalmente se convirtió en
Ya no necesita una sola función de then
invocación o devolución de llamada, ya que en una función asincrónica (que devuelve una promesa cuando se la 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, 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
}
Mientras esperábamos ES8, ya usamos un tipo de sintaxis muy similar. ES6 vino con funciones de generador , que permiten dividir la ejecución en pedazos con yield
palabras clave colocadas arbitrariamente . Esos segmentos se pueden ejecutar uno tras otro, de forma independiente, incluso asincrónica, y eso es lo que hacemos cuando queremos esperar la resolución de una promesa antes de ejecutar el siguiente paso.
Hay bibliotecas dedicadas (como co o task.js ), pero también muchas bibliotecas de promesas tienen funciones auxiliares ( Q , Bluebird , when , ...) que hacen esta ejecución asíncrona paso a paso cuando les das una función generadora 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.
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 generadores y funciones asíncronas .
Y luego, también hay muchos otros lenguajes de compilación en JS
que se dedican a facilitar la programación asincrónica. En general, utilizan una sintaxis similar a await
, (por ejemplo, helado CoffeeScript ), pero también hay otros que cuentan con una Haskell-como do
-notation (por ejemplo LatteJs , monádico , Purescript o LispyScript ).
Asignar promesas para valores necesarios posteriores a variables y luego obtener su valor mediante inspección sincrónica. El ejemplo utiliza el .value()
método de 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 utilizar 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();
});
}
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 de éxito) es la solución natural de JavaScript. Con las promesas, podemos anidar y aplanar arbitrariamente las .then()
devoluciones de llamada; son semánticamente equivalentes, excepto por el alcance de la interna.
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 está construyendo una pirámide de sangría. Si la sangría se vuelve 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 método 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
}
Similar a anidar las devoluciones de llamada, esta técnica se basa en cierres. Sin embargo, la cadena permanece plana: en lugar de pasar solo el último resultado, se pasa algún objeto de estado para cada paso. Estos objetos de estado acumulan los resultados de las acciones anteriores, transmitiendo todos los valores que se necesitarán más tarde nuevamente 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 resultA
y pasa una matriz de ambos resultados al siguiente paso. Que usa la sintaxis de desestructuración de parámetros para dividirlo en variables individuales nuevamente.
Antes de que la desestructuración estuviera disponible con ES6, .spread()
muchas bibliotecas de promesas proporcionaron un ingenioso método auxiliar llamado ( Q , Bluebird , when ,…). Se necesita una función con varios parámetros, uno para cada elemento de la matriz, que se utilizará 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 de 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 es posible que no solo use matrices, sino objetos arbitrariamente complejos. Por ejemplo, con _.extend
o 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ícitos pueden mejorar la claridad, se volverá tedioso para una cadena larga. Especialmente cuando solo necesita el estado esporádicamente, aún debe pasarlo en cada paso. Con esta interfaz fija, las devoluciones de llamada únicas en la cadena están estrechamente acopladas y son inflexibles al cambio. Hace que la factorización de pasos individuales sea más difícil y las devoluciones de llamada no se pueden proporcionar directamente desde otros módulos; siempre deben incluirse en un código repetitivo que se preocupe por el estado. Las funciones de ayuda abstractas como las anteriores pueden aliviar un poco el dolor, pero siempre estarán presentes.
La solución trivial (pero poco elegante y bastante propensa a errores) es simplemente usar variables de mayor alcance (a las que tienen acceso todas las devoluciones de llamada en la cadena) y escribir valores de resultado en ellas 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:
La biblioteca Bluebird fomenta el uso de un objeto que se transmite, utilizando su bind()
método 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 la this
palabra clave inutilizable . Si bien las propiedades de los objetos son más propensas a errores tipográficos no detectados que 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 de promesas que no admiten .bind (aunque de una manera algo más detallada y no se puede 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));
}
El uso de un objeto de ámbito local para recopilar los resultados intermedios en una cadena de promesas es un enfoque razonable para 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);
});
}
El nodo 7.4 ahora admite llamadas asíncronas / en espera con la bandera 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 conocido a 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.
Según cómo-encadenar-las-promesas-de-JavaScript
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);
});
Otra respuesta, usando la babel-node
versión <6
Utilizando 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()
Entonces, ¡corre babel-node example.js
y listo!
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 Mongoose prometido.
var globalVar = '';
User.findAsync({}).then(function(users){
globalVar = users;
}).then(function(){
console.log(globalVar);
});
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');
})
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>
Al usar bluebird, puede usar el .bind
método 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;
});
Consulte este enlace para obtener más información:
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
Creo que puedes usar el hash de RSVP.
Algo parecido a lo siguiente:
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);
});
Solución:
Puede poner valores intermedios en el alcance en cualquier función posterior 'luego' explícitamente, utilizando 'bind'. Es una buena solución que no requiere cambiar la forma en que funcionan las Promesas, y solo requiere una línea o dos de código para propagar los valores al igual que los errores ya se propagan.
Aquí tienes 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).
La estrella de HGTV, Christina Hall, revela que le diagnosticaron envenenamiento por mercurio y plomo, probablemente debido a su trabajo como manipuladora de casas.
Recientemente salió a la luz un informe policial que acusa a la estrella de 'Love Is Blind', Brennon, de violencia doméstica. Ahora, Brennon ha respondido a los reclamos.
Conozca cómo Wynonna Judd se dio cuenta de que ahora es la matriarca de la familia mientras organizaba la primera celebración de Acción de Gracias desde que murió su madre, Naomi Judd.
Descubra por qué un destacado experto en lenguaje corporal cree que es fácil trazar "tales paralelismos" entre la princesa Kate Middleton y la princesa Diana.
Los inodoros arrojan columnas de aerosol invisibles con cada descarga. ¿Como sabemos? La prueba fue capturada por láseres de alta potencia.
Air travel is far more than getting from point A to point B safely. How much do you know about the million little details that go into flying on airplanes?
The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!
¿Sigue siendo efectivo ese lote de repelente de insectos que te quedó del verano pasado? Si es así, ¿por cuánto tiempo?
Si este tráiler de pesadilla de la temporada más reciente de Great British Bake Off te asustó y te hizo no volver a ver el programa, es posible que tengas suerte: PBS no ha decidido si se transmitirá o no la última temporada en los Estados Unidos. actualización, para aquellos que no siguen sin aliento este tipo de drama de nicho: el presentador Paul Hollywood y la hermosa carpa llena de batidoras de colores pastel y cuadros se trasladaron de la BBC al Canal 4; Mary Berry, Sue Perkins y Mel Giedroyc renunciaron.
Foto: Netflix Oh, Hello On Broadway (Netflix): Después de llegar a Broadway el año pasado, los dos locos del Upper West Side interpretados por John Mulaney y Nick Kroll finalmente llegaron a Netflix. El especial consta del espectáculo en el escenario, algunos momentos entre bastidores y un invitado muy especial de “Too Much Tuna”.
¿Quiere probar un cepillo de dientes Sonicare sin gastar mucho dinero en uno de sus modelos favoritos de gama alta? Puede comprar un kit de la Serie 2 o Serie 3 por tan solo $ 30 hoy en Amazon. Haga clic aquí para ver la lista completa de modelos elegibles y tenga en cuenta que se descontarán $ 10 adicionales en su carrito.
Tapas elásticas de silicona de Tomorrow's Kitchen, paquete de 12 | $14 | Amazonas | Código promocional 20OFFKINJALids son básicamente los calcetines de la cocina; siempre perdiéndose, dejando contenedores huérfanos que nunca podrán volver a cerrarse. Pero, ¿y si sus tapas pudieran estirarse y adaptarse a todos los recipientes, ollas, sartenes e incluso frutas en rodajas grandes que sobran? Nunca más tendrás que preocuparte por perder esa tapa tan específica.
El equipo está a la espera de las medallas que ganó en los Juegos Olímpicos de Invierno de 2022 en Beijing, ya que se está resolviendo un caso de dopaje que involucra a la patinadora artística rusa Kamila Valieva.
Miles de compradores de Amazon recomiendan la funda de almohada de seda Mulberry, y está a la venta en este momento. La funda de almohada de seda viene en varios colores y ayuda a mantener el cabello suave y la piel clara. Compre las fundas de almohada de seda mientras tienen hasta un 46 por ciento de descuento en Amazon
El jueves se presentó una denuncia de delito menor amenazante agravado contra Joe Mixon.
El Departamento de Policía de Lafayette comenzó a investigar a un profesor de la Universidad de Purdue en diciembre después de recibir varias denuncias de un "hombre sospechoso que se acercaba a una mujer".
Al igual que el mundo que nos rodea, el lenguaje siempre está cambiando. Mientras que en eras anteriores los cambios en el idioma ocurrían durante años o incluso décadas, ahora pueden ocurrir en cuestión de días o incluso horas.
Estoy de vuelta por primera vez en seis años. No puedo decirte cuánto tiempo he estado esperando esto.
“And a river went out of Eden to water the garden, and from thence it was parted and became into four heads” Genesis 2:10. ? The heart is located in the middle of the thoracic cavity, pointing eastward.
Creo, un poco tarde en la vida, en dar oportunidades a la gente. Generosamente.