He reestructurado mi código a promesas , y he creado una maravillosa cadena de promesa larga y plana , que consta de múltiples .then()
devoluciones de llamada. 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?
});
}
javascript
, es relevante en otro idioma. Solo uso la respuesta "romper la cadena" en java y jdeferredRespuestas:
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 directo, una composición clara de funcionalidades y, por lo tanto, una fácil modularización.
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 lathen
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 presenta una
join
función dedicada para reemplazar esa combinaciónPromise.all
+spread
con una construcción más simple (y más eficiente):fuente
promiseA
ypromiseB
son las funciones (de devolución de promesas) aquí.spread
fue muy útil en este patrón. Para soluciones más modernas vea la respuesta aceptada. Sin embargo, ya actualicé la respuesta de paso explícito , y realmente no hay una buena razón para no actualizar esta también.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 asíncronas finalmente se convirtió en
ECMAScript 8
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 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í: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
yield
palabras clave colocadas arbitrariamente . Esos segmentos se pueden ejecutar uno tras otro, de forma independiente, incluso de forma asincrónica, y eso es justo 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 , cuándo , ...) que realizan esta ejecución paso a paso asíncrona cuando les das una función de generador que rinde promesas.
Esto funcionó en Node.js desde la versión 4.0, también algunos navegadores (o sus ediciones de desarrollo) 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 generadores 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. En general, utilizan una sintaxis similar a
await
, (por ejemplo, helado CoffeeScript ), pero también hay otros que cuentan con una Haskell-comodo
-notation (por ejemplo LatteJs , monádico , Purescript o LispyScript ).fuente
getExample
sigue siendo una función que devuelve una promesa, funciona igual que las funciones en las otras respuestas, pero con una sintaxis más agradable. Podríaawait
llamar a otraasync
función, o podría encadenar.then()
a su resultado.steps.next().value.then(steps.next)...
pero eso no funcionó.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
.value()
método de bluebird, pero muchas bibliotecas proporcionan un método similar.Esto se puede usar para tantos valores como desee:
fuente
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 arbitrariamente las
.then()
devoluciones de llamada: son semánticamente equivalentes, excepto por el alcance del interno.Por supuesto, esto es construir una pirámide de sangría. Si la sangría se está haciendo 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.
También puede usar funciones de ayuda para este tipo de aplicación parcial , como
_.partial
por ejemplo Underscore / lodash o el método nativo.bind()
, para disminuir aún más la sangría:fuente
bind
función en Mónadas. Haskell proporciona azúcar sintáctica (notación do) para que parezca una sintaxis asíncrona / en espera.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.
Aquí, esa pequeña flecha
b => [resultA, b]
es la función que se cierraresultA
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,
.spread()
muchas bibliotecas prometedoras proporcionaron un ingenioso método auxiliar llamado ( Q , Bluebird , cuándo , ...). 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,
Alternativamente, puede emplear
Promise.all
para producir la promesa de la matriz:Y no solo puede usar matrices, sino también objetos complejos arbitrariamente. Por ejemplo, con
_.extend
oObject.assign
en una función auxiliar diferente: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. Funciones auxiliares abstractas como las anteriores pueden aliviar un poco el dolor, pero siempre estará presente.
fuente
Promise.all
que deba alentarse la sintaxis que omite el (no funcionará en ES6 cuando la desestructuración lo reemplace y cambiar a.spread
a athen
menudo da resultados inesperados a las personas. A partir de ahora, no estoy seguro de por qué necesita para uso augment - añadir cosas al prototipo promesa no es una forma aceptable para extender promesas ES6 todos modos que se supone que debe ser ampliado con (el no soportado actualmente) de subclases.Promise.all
"? Ninguno de los métodos en esta respuesta se romperá con ES6. Cambiarspread
a una desestructuraciónthen
tampoco debería tener problemas. Re .prototype.augment: Sabía que alguien lo notaría, solo me gustaba explorar las posibilidades, ir a editarlo.return [x,y]; }).spread(...
lugar dereturn Promise.all([x, y]); }).spread(...
lo que no cambiaría al cambiar la extensión por es6 azúcar desestructurante y tampoco sería un caso marginal extraño donde las promesas tratan las matrices de retorno de manera diferente a todo lo demás.Estado contextual mutable
La solución trivial (pero poco elegante y bastante propensa a errores) es usar 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:
En lugar de muchas variables, también se podría 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 pasa, 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 lathis
palabra clave que de otro modo no se podría utilizar . Si bien las propiedades del objeto son más propensas a errores tipográficos no detectados que a las variables, el patrón es bastante inteligente: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):
fuente
.bind()
es innecesario para prevenir la pérdida de memoriaUn 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:
fuente
Promise
constructor antipatrón !El nodo 7.4 ahora admite llamadas asíncronas / en espera con el indicador de armonía.
Prueba esto:
y ejecuta el archivo con:
node --harmony-async-await getExample.js
¡Tan simple como puede ser!
fuente
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 cómo-encadenar-javascript-promesas
ok, veamos el código:
fuente
.then
se pide, sino los resultados anteriores. Por ejemplo,thirdPromise
acceder al resultado defirstPromise
.Otra respuesta, usando la
babel-node
versión <6Utilizando
async - await
npm install -g [email protected]
example.js:
Entonces, corre
babel-node example.js
y listo!fuente
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.
fuente
globalVar
nada, ¿solo necesitasUser.findAsync({}).then(function(users){ console.log(users); mongoose.connection.close() });
?Otra respuesta, usando el ejecutor secuencial nsynjs :
Actualización: ejemplo de trabajo agregado
fuente
Cuando use bluebird, puede usar el
.bind
método para compartir variables en la cadena de promesa:por favor revise este enlace para más información:
http://bluebirdjs.com/docs/api/promise.bind.html
fuente
manera fácil: D
fuente
Creo que puedes usar hash de RSVP.
Algo así como a continuación:
fuente
Promise.all
solución , solo con un objeto en lugar de una matriz.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 el funcionamiento de Promises, 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:
Esta solución se puede invocar de la siguiente manera:
(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).
fuente
async
/await
todavía significa usar promesas. Lo que puede abandonar son lasthen
llamadas con devoluciones de llamada.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
Transpilado de un proyecto mecanografiado:
https://github.com/cancerberoSgx/misc-utils-of-mine/blob/2927c2477839f7b36247d054e7e50abe8a41358b/misc-utils-of-mine-generic/src/promise.ts#L31
Para casos más complejos, a menudo utilizo estas pequeñas utilidades de promesas 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=
fuente