Recientemente me encontré con una situación determinada un par de veces, que no sabía cómo resolver correctamente. Suponga el siguiente código:
somethingAsync()
.then( afterSomething )
.then( afterSomethingElse )
function afterSomething( amazingData ) {
return processAsync( amazingData );
}
function afterSomethingElse( processedData ) {
}
Ahora puede surgir una situación donde me gustaría tener acceso a amazingData
en afterSomethingElse
.
Una solución obvia sería devolver una matriz o un hash de afterSomething
, porque, bueno, solo puede devolver un valor de una función. Pero me pregunto si hay forma de afterSomethingElse
aceptar 2 parámetros e invocarlos de la misma manera, ya que parece mucho más fácil de documentar y comprender.
Solo me pregunto acerca de esta posibilidad ya que la hay Q.spread
, que hace algo similar a lo que quiero.
javascript
promise
q
Der Hochstapler
fuente
fuente
Respuestas:
No puede resolver una promesa con varias propiedades del mismo modo que no puede devolver varios valores de una función . Una promesa representa conceptualmente un valor a lo largo del tiempo, por lo que si bien puede representar valores compuestos, no puede poner varios valores en una promesa.
Una promesa se resuelve de forma inherente con un valor único: esto es parte de cómo funciona Q, cómo funciona la especificación Promises / A + y cómo funciona la abstracción .
Lo más cercano que puede obtener es usar
Q.spread
y devolver matrices o usar la desestructuración de ES6 si es compatible o si está dispuesto a usar una herramienta de transpilación como BabelJS.En cuanto a transmitir el contexto a través de una cadena de promesas, consulte el excelente canónico de Bergi al respecto .
fuente
.spread()
muestre como Bluebird en esta respuesta relacionada: stackoverflow.com/a/22776850/1624862Promise.all([a, b, c]).then(function(x, y, z) {...})
funciona correctamente en todos los motores Javascript modernos con x, yyz evaluando los valores resueltos de a, by c. Por lo tanto, es más exacto decir que el lenguaje no le permite hacerlo fácilmente (o con cordura) desde el código de usuario (porque puede devolver una Promesa directamente desde una cláusula then, puede envolver sus valores en promesas y luego envolverlas con Promise .all () para obtener el comportamiento deseado, aunque de una manera complicada).Promise.all
cumple con una matriz . EnPromise.all([a,b]).then((a, b) =>
b
esundefined
. Es por eso que debes hacer lo.then(([a, b]) =>
que es una tarea de desestructuraciónsolo puede pasar un valor, pero puede ser una matriz con múltiples valores dentro, como ejemplo:
function step1(){ let server = "myserver.com"; let data = "so much data, very impresive"; return Promise.resolve([server, data]); }
Por otro lado, se puede utilizar la desestructuración de expresión para ES2015 para obtener los valores individuales.
function step2([server, data]){ console.log(server); // print "myserver.com" console.log(data); // print "so much data, very impresive" return Promise.resolve("done"); }
para llamar a ambos promesa, encadenándolos:
step1() .then(step2) .then((msg)=>{ console.log(msg); // print "done" })
fuente
function step2([server, data]) { …
- de esa manera también evita asignar a globales implícitos. Y realmente debería usarreturn
oPromise.resolve
, no elnew Promise
constructor en sus ejemplos.Puede devolver un objeto que contenga ambos valores; no hay nada de malo en eso.
Otra estrategia es mantener el valor, mediante cierres, en lugar de pasarlo por:
somethingAsync().then(afterSomething); function afterSomething(amazingData) { return processAsync(amazingData).then(function (processedData) { // both amazingData and processedData are in scope here }); }
Forma completa en lugar de parcialmente en línea (equivalente, posiblemente más consistente):
somethingAsync().then(function (amazingData) { return processAsync(amazingData).then(function (processedData) { // both amazingData and processedData are in scope here }); }
fuente
then
interior a otrothen
? ¿No es un anti-patrón ?Dos cosas que puedes hacer, devolver un objeto
somethingAsync() .then( afterSomething ) .then( afterSomethingElse ); function processAsync (amazingData) { //processSomething return { amazingData: amazingData, processedData: processedData }; } function afterSomething( amazingData ) { return processAsync( amazingData ); } function afterSomethingElse( dataObj ) { let amazingData = dataObj.amazingData, processedData = dataObj.proccessedData; }
¡Usa el alcance!
var amazingData; somethingAsync() .then( afterSomething ) .then( afterSomethingElse ) function afterSomething( returnedAmazingData ) { amazingData = returnedAmazingData; return processAsync( amazingData ); } function afterSomethingElse( processedData ) { //use amazingData here }
fuente
Así es como creo que debería estar haciendo.
dividiendo la cadena
Debido a que ambas funciones usarán amazingData , tiene sentido tenerlas en una función dedicada. Por lo general, hago eso cada vez que quiero reutilizar algunos datos, por lo que siempre está presente como una función arg.
Como su ejemplo está ejecutando algún código, supongo que todo está declarado dentro de una función. Lo llamaré toto () . Luego tendremos otra función que se ejecutará tanto afterSomething () como afterSomethingElse () .
function toto() { return somethingAsync() .then( tata ); }
También notará que agregué una declaración de devolución , ya que generalmente es el camino a seguir con Promesas: siempre devuelve una promesa para que podamos seguir encadenando si es necesario. Aquí, somethingAsync () producirá datos asombrosos y estará disponible en todas partes dentro de la nueva función.
Ahora bien, ¿cómo se verá esta nueva función normalmente depende de si processAsync () también es asincrónico ?
processAsync no asincrónico
No hay razón para complicar demasiado las cosas si processAsync () no es asincrónico. Algún código secuencial bueno y antiguo lo haría.
function tata( amazingData ) { var processed = afterSomething( amazingData ); return afterSomethingElse( amazingData, processed ); } function afterSomething( amazingData ) { return processAsync( amazingData ); } function afterSomethingElse( amazingData, processedData ) { }
Tenga en cuenta que no importa si afterSomethingElse () está haciendo algo asincrónico o no. Si lo hace, se devolverá una promesa y la cadena podrá continuar. Si no es así, se devolverá el valor del resultado. Pero debido a que la función se llama desde un then () , el valor se incluirá en una promesa de todos modos (al menos en Javascript sin formato).
processAsync asincrónico
Si processAsync () es asincrónico, el código se verá ligeramente diferente. Aquí consideramos que afterSomething () y afterSomethingElse () no se reutilizarán en ningún otro lugar.
function tata( amazingData ) { return afterSomething() .then( afterSomethingElse ); function afterSomething( /* no args */ ) { return processAsync( amazingData ); } function afterSomethingElse( processedData ) { /* amazingData can be accessed here */ } }
Lo mismo que antes para afterSomethingElse () . Puede ser asincrónico o no. Se devolverá una promesa o un valor envuelto en una promesa resuelta.
Su estilo de codificación es bastante parecido al que solía hacer, por eso respondí incluso después de 2 años. No soy un gran fanático de tener funciones anónimas en todas partes. Me cuesta leer. Incluso si es bastante común en la comunidad. Es como reemplazamos el infierno de devolución de llamada por un purgatorio de promesas .
También me gusta mantener el nombre de las funciones en el entonces corto. De todos modos, solo se definirán localmente. Y la mayoría de las veces llamarán a otra función definida en otro lugar, tan reutilizable, para hacer el trabajo. Incluso hago eso para funciones con solo 1 parámetro, por lo que no necesito que la función entre y salga cuando agrego / elimino un parámetro a la firma de la función.
Ejemplo de comer
Aquí hay un ejemplo:
function goingThroughTheEatingProcess(plenty, of, args, to, match, real, life) { return iAmAsync() .then(chew) .then(swallow); function chew(result) { return carefullyChewThis(plenty, of, args, "water", "piece of tooth", result); } function swallow(wine) { return nowIsTimeToSwallow(match, real, life, wine); } } function iAmAsync() { return Promise.resolve("mooooore"); } function carefullyChewThis(plenty, of, args, and, some, more) { return true; } function nowIsTimeToSwallow(match, real, life, bobool) { }
No se centre demasiado en Promise.resolve () . Es solo una forma rápida de crear una promesa resuelta. Lo que trato de lograr con esto es tener todo el código que estoy ejecutando en una sola ubicación, justo debajo de los thens . Todas las demás funciones con un nombre más descriptivo son reutilizables.
El inconveniente de esta técnica es que define muchas funciones. Pero me temo que es un dolor necesario para evitar tener funciones anónimas por todas partes. ¿Y cuál es el riesgo de todos modos: un desbordamiento de pila? (¡broma!)
El uso de matrices u objetos como se define en otras respuestas también funcionaría. Esta de alguna manera es la respuesta propuesta por Kevin Reid .
También puede utilizar bind () o Promise.all () . Tenga en cuenta que aún requerirán que divida su código.
usando bind
Si desea mantener sus funciones reutilizables pero realmente no necesita mantener lo que está dentro del entonces muy corto, puede usar bind () .
function tata( amazingData ) { return afterSomething( amazingData ) .then( afterSomethingElse.bind(null, amazingData) ); } function afterSomething( amazingData ) { return processAsync( amazingData ); } function afterSomethingElse( amazingData, processedData ) { }
Para mantenerlo simple, bind () antepondrá la lista de argumentos (excepto el primero) a la función cuando se llame.
usando Promise.all
En su publicación mencionó el uso de spread () . Nunca usé el marco que está usando, pero así es como debería poder usarlo.
Algunos creen que Promise.all () es la solución a todos los problemas, así que supongo que merece ser mencionado.
function tata( amazingData ) { return Promise.all( [ amazingData, afterSomething( amazingData ) ] ) .then( afterSomethingElse ); } function afterSomething( amazingData ) { return processAsync( amazingData ); } function afterSomethingElse( args ) { var amazingData = args[0]; var processedData = args[1]; }
Puede pasar datos a Promise.all () - tenga en cuenta la presencia de la matriz - siempre que las promesas, pero asegúrese de que ninguna de las promesas falle; de lo contrario, dejará de procesarse.
Y en lugar de definir nuevas variables a partir del argumento args , debería poder usar spread () en lugar de then () para todo tipo de trabajo increíble.
fuente
Simplemente crea un objeto y extrae argumentos de ese objeto.
let checkIfNumbersAddToTen = function (a, b) { return new Promise(function (resolve, reject) { let c = parseInt(a)+parseInt(b); let promiseResolution = { c:c, d : c+c, x : 'RandomString' }; if(c===10){ resolve(promiseResolution); }else { reject('Not 10'); } }); };
Extraiga argumentos de promiseResolution.
checkIfNumbersAddToTen(5,5).then(function (arguments) { console.log('c:'+arguments.c); console.log('d:'+arguments.d); console.log('x:'+arguments.x); },function (failure) { console.log(failure); });
fuente
Todo lo que devuelva de una promesa se incluirá en una promesa que se desenvolverá en la siguiente
.then()
etapa.Se vuelve interesante cuando necesita devolver una o más promesas junto con uno o más valores sincrónicos como;
Promise.resolve([Promise.resolve(1), Promise.resolve(2), 3, 4]) .then(([p1,p2,n1,n2]) => /* p1 and p2 are still promises */);
En estos casos, sería esencial utilizar
Promise.all()
para obtenerp1
yp2
promesas desenvueltas en la siguiente.then()
etapa, comoPromise.resolve(Promise.all([Promise.resolve(1), Promise.resolve(2), 3, 4])) .then(([p1,p2,n1,n2]) => /* p1 is 1, p2 is 2, n1 is 3 and n2 is 4 */);
fuente
Puede marcar Observable representado por Rxjs , le permite devolver más de un valor.
fuente
Simplemente devuelva una tupla:
async add(dto: TDto): Promise<TDto> { console.log(`${this.storeName}.add(${dto})`); return firebase.firestore().collection(this.dtoName) .withConverter<TDto>(this.converter) .add(dto) .then(d => [d.update(this.id, d.id), d.id] as [any, string]) .then(x => this.get(x[1]));
}
fuente