¿Cómo accedo a resultados de promesa anteriores en una cadena .then ()?


650

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?
    });
}

2
Esta pregunta es realmente interesante e incluso si está etiquetada javascript, es relevante en otro idioma. Solo uso la respuesta "romper la cadena" en java y jdeferred
gontard

Respuestas:


377

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.

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.allque sólo estaba disponible con ES6, ES5 en la thenllamada 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 joinfunción dedicada para reemplazar esa combinación Promise.all+ spreadcon una construcción más simple (y más eficiente):


return Promise.join(a, b, function(resultA, resultB) {  });

1
¿Las funciones dentro de la matriz se ejecutan en orden?
scaryguy

66
@scaryguy: No hay funciones en la matriz, esas son promesas. promiseAy promiseBson las funciones (de devolución de promesas) aquí.
Bergi

2
@Roland nunca dijo que era :-) Esta respuesta fue escrita en la era de ES5 donde no había promesas en el estándar, y spreadfue 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.
Bergi

1
@reify No, no deberías hacer eso , traería problemas con los rechazos.
Bergi

1
No entiendo este ejemplo. Si hay una cadena de declaraciones 'entonces' que requieren que los valores se propaguen por toda la cadena, no veo cómo esto resuelve el problema. Una promesa que requiere un valor anterior NO PUEDE ser activada (creada) hasta que ese valor esté presente. Además, Promise.all () simplemente espera a que finalicen todas las promesas de su lista: no impone un pedido. Así que necesito que cada función 'siguiente' tenga acceso a todos los valores anteriores y no veo cómo su ejemplo lo hace. Debes mostrarnos tu ejemplo, porque no lo creo ni lo entiendo.
David Spector

238

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 theninvocació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 yieldpalabras 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.

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 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-como do-notation (por ejemplo LatteJs , monádico , Purescript o LispyScript ).


@ Bergi, ¿necesita esperar la función asincrónica examle getExample () del código externo?
arisalexis

@arisalexis: Sí, getExamplesigue 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ía awaitllamar a otra asyncfunción, o podría encadenar .then()a su resultado.
Bergi

1
Tengo curiosidad, ¿por qué respondiste tu propia pregunta inmediatamente después de formularla? Aquí hay una buena discusión, pero tengo curiosidad. ¿Quizás encontraste tus respuestas por tu cuenta después de preguntar?
granmoe

@granmoe: publiqué toda la discusión a propósito como un objetivo duplicado canónico
Bergi

¿Hay alguna forma (no demasiado laboriosa) de evitar usar Promise.coroutine (es decir, no usar Bluebird u otra biblioteca, sino solo JS simple) en el ejemplo ECMAScript 6 con la función de generador? Tenía en mente algo así, steps.next().value.then(steps.next)...pero eso no funcionó.
un alumno no tiene nombre el

102

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.

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();
    });
}

66
Esta es mi respuesta favorita: confianza legible, extensible y mínima en las funciones de biblioteca o lenguaje
Jason

13
@ Jason: Uh, "¿ dependencia mínima de las características de la biblioteca "? La inspección sincrónica es una función de biblioteca, y una bastante no estándar para arrancar.
Bergi

2
Creo que se refería a características específicas de la biblioteca
deathgaze

54

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.

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á 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.

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 de ayuda para este tipo de aplicación parcial , como _.partialpor ejemplo 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
}

55
Esta misma sugerencia se da como la solución al 'Error avanzado n. ° 4' en el artículo de Nolan Lawson sobre promesas pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html . Es una buena lectura.
Robert

2
Esta es exactamente la bindfunción en Mónadas. Haskell proporciona azúcar sintáctica (notación do) para que parezca una sintaxis asíncrona / en espera.
zeronone

50

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 resultAy 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,

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.allpara 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 no solo puede usar matrices, sino también objetos complejos arbitrariamente. Por ejemplo, con _.extendo Object.assignen 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. Funciones auxiliares abstractas como las anteriores pueden aliviar un poco el dolor, pero siempre estará presente.


Primero, no creo Promise.allque deba alentarse la sintaxis que omite el (no funcionará en ES6 cuando la desestructuración lo reemplace y cambiar a .spreada a thenmenudo 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.
Benjamin Gruenbaum

@BenjaminGruenbaum: ¿Qué quieres decir con " omisión de sintaxisPromise.all "? Ninguno de los métodos en esta respuesta se romperá con ES6. Cambiar spreada una desestructuración thentampoco debería tener problemas. Re .prototype.augment: Sabía que alguien lo notaría, solo me gustaba explorar las posibilidades, ir a editarlo.
Bergi

Por sintaxis de matriz quiero decir, en return [x,y]; }).spread(...lugar de return 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.
Benjamin Gruenbaum

3
Esta es probablemente la mejor respuesta. Las promesas son la luz de "Programación Reactiva Funcional", y esta es a menudo la solución empleada. Por ejemplo, BaconJs, tiene #combineTemplate que le permite combinar resultados en un objeto que se pasa por la cadena
U Avalos

1
@CapiEtheriel La respuesta se escribió cuando ES6 no estaba tan extendido como hoy. Sí, tal vez sea hora de cambiar los ejemplos
Bergi

35

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:

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 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:

  • El estado mutable es feo y las variables globales son malvadas .
  • 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 salir del 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) en las que 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 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 thispalabra 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:

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));
}

.bind()es innecesario para prevenir la pérdida de memoria
Esailija

@Esailija: ¿Pero la promesa devuelta no tiene una referencia al objeto de contexto? OK, por supuesto, la recolección de basura lo manejará más tarde; no es una "fuga" a menos que la promesa nunca se elimine.
Bergi

Sí, pero las promesas también hacen referencia a sus valores de cumplimiento y razones de error ... pero nada tiene referencia a la promesa, por lo que no importa
Esailija

44
¡Por favor divida esta respuesta en dos, ya que casi voté por el preámbulo! Creo que "la solución trivial (pero poco elegante y más bien propensa a errores)" es la solución más limpia y simple, ya que no se basa más en los cierres y el estado mutable que su auto-respuesta aceptada, pero es más simple. Los cierres no son globales ni malvados. Los argumentos dados contra este enfoque no tienen sentido para mí dada la premisa. ¿Qué problemas de modularización puede darse a una "maravillosa cadena de promesa larga y plana"?
jib

2
Como dije anteriormente, las promesas son "luz funcional de programación reactiva". Este es un antipatrón en FRP
U Avalos

15

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.

1
¡Al menos evita el Promiseconstructor antipatrón !
Bergi

Gracias @ Bergi, ¡ni siquiera me di cuenta de que era un antipatrón hasta que lo mencionaste!
Jay

esta es una buena solución para mitigar el error relacionado con la promesa. Estaba usando ES5 y no quería agregar otra biblioteca para trabajar con la promesa.
nilakantha singh deo

8

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!


8

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:

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);
    });

44
Esto realmente no responde la pregunta sobre cómo acceder a resultados anteriores en la cadena.
Bergi

2
Cada promesa puede obtener el valor anterior, ¿cuál es su significado?
yzfdjzwl

1
Echa un vistazo al código en la pregunta. El objetivo no es obtener el resultado de la promesa que .thense pide, sino los resultados anteriores. Por ejemplo, thirdPromiseacceder al resultado de firstPromise.
Bergi

6

Otra respuesta, usando la babel-nodeversión <6

Utilizando async - await

npm install -g babel@5.6.14

example.js:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

Entonces, corre babel-node example.jsy listo!


1
Sí, lo hice, justo después de publicar el mío. Aún así, lo dejaré porque explica cómo comenzar a usar ES7 en lugar de decir que algún día ES7 estará disponible.
Anthony

1
Ah, claro, debería actualizar mi respuesta para decir que los complementos "experimentales" para estos ya están aquí.
Bergi

2

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);
});

2
Tenga en cuenta que este patrón ya está detallado en la respuesta de estado contextual Mutable (y también por qué es feo, tampoco soy un gran admirador)
Bergi

Sin embargo, en su caso, el patrón parece ser inútil. No necesitas globalVarnada, ¿solo necesitas User.findAsync({}).then(function(users){ console.log(users); mongoose.connection.close() });?
Bergi

1
No lo necesito personalmente en mi propio código, pero el usuario puede necesitar ejecutar más asíncrono en la segunda función y luego interactuar con la llamada original de Promise. Pero como se mencionó, usaré generadores en este caso. :)
Anthony

2

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>


1

Cuando use bluebird, puede usar el .bindmé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;
});

por favor revise este enlace para más información:

http://bluebirdjs.com/docs/api/promise.bind.html


Tenga en cuenta que este patrón ya se detalla en la respuesta de estado contextual Mutable
Bergi

1
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


¿Has notado esta respuesta ?
Bergi

1

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);
    });

Sí, eso es lo mismo que la Promise.allsolución , solo con un objeto en lugar de una matriz.
Bergi

0

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:

// 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).


Este parece ser el mismo patrón que en la respuesta de cierre de anidación (y)
Bergi

Se ve similar. Desde entonces he aprendido que la nueva sintaxis Async / Await incluye la vinculación automática de argumentos, por lo que todos los argumentos están disponibles para todas las funciones asincrónicas. Estoy abandonando las promesas.
David Spector

async/ awaittodavía significa usar promesas. Lo que puede abandonar son las thenllamadas con devoluciones de llamada.
Bergi

-1

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:

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=


¿Parece que estás sugiriendo un estado contextual mutable o una inspección sincrónica ?
Bergi

@bergi La primera vez que encabezo esos nombres, agrego a la lista, gracias. Conozco este tipo de promesas conscientes con el nombre de Diferido. Por cierto, la implementación es solo una promesa con una resolución envuelta. A menudo necesito este patrón en aquellos casos en que la responsabilidad de la creación y resolución de promesas es independiente, por lo que no es necesario relacionarlos solo para resolver una promesa. Me adapté, pero no para su ejemplo, y usando una clase, pero tal vez equivalente.
cancerbero
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.