son - promise javascript w3schools
Resolver promete uno tras otro(es decir, en secuencia)? (19)
Considere el siguiente código que lee una matriz de archivos de manera serial / secuencial. readFiles
devuelve una promesa, que se resuelve solo una vez que todos los archivos se han leído en secuencia.
var Q = require("q");
var readFile = function(file) {
... // Returns a promise.
};
var readFiles = function(files) {
var deferred = Q.defer();
var readSequential = function(index) {
if (index >= files.length) {
deferred.resolve();
} else {
readFile(files[index]).then(function() {
readSequential(index + 1);
});
}
};
readSequential(0); // Start!
return deferred.promise;
};
El código del código anterior funciona, pero no me gusta tener que recurrir para que las cosas ocurran de forma secuencial. ¿Hay alguna manera más sencilla de reescribir este código para que no tenga que usar mi extraña función readSequential
?
Originalmente traté de usar Q.all
, pero eso causó que todas las llamadas de readFile
ocurrieran simultáneamente, que no es lo que quiero:
var readFiles = function(files) {
return Q.all(files.map(function(file) {
return readFile(file);
}));
};
Aquí es cómo prefiero ejecutar tareas en serie.
function runSerial() {
var that = this;
// task1 is a function that returns a promise (and immediately starts executing)
// task2 is a function that returns a promise (and immediately starts executing)
return Promise.resolve()
.then(function() {
return that.task1();
})
.then(function() {
return that.task2();
})
.then(function() {
console.log(" ---- done ----");
});
}
¿Qué pasa con los casos con más tareas? Me gusta, 10?
function runSerial(tasks) {
var result = Promise.resolve();
tasks.forEach(task => {
result = result.then(() => task());
});
return result;
}
Creé este método simple en el objeto Promesa:
Crear y agregar un método Promise.sequence al objeto Promise
Promise.sequence = function (chain) {
var results = [];
var entries = chain;
if (entries.entries) entries = entries.entries();
return new Promise(function (yes, no) {
var next = function () {
var entry = entries.next();
if(entry.done) yes(results);
else {
results.push(entry.value[1]().then(next, function() { no(results); } ));
}
};
next();
});
};
Uso:
var todo = [];
todo.push(firstPromise);
if (someCriterium) todo.push(optionalPromise);
todo.push(lastPromise);
// Invoking them
Promise.sequence(todo)
.then(function(results) {}, function(results) {});
Lo mejor de esta extensión del objeto Promesa es que es consistente con el estilo de las promesas. Promise.all y Promise.sequence se invocan de la misma manera, pero tienen una semántica diferente.
Precaución
La ejecución secuencial de promesas no suele ser una muy buena forma de usar las promesas. Por lo general, es mejor utilizar Promise.all y permitir que el navegador ejecute el código lo más rápido posible. Sin embargo, existen casos de uso real para él, por ejemplo, cuando se escribe una aplicación móvil usando javascript.
Esta es una pequeña variación de otra respuesta anterior. Usando Promesas nativas:
function inSequence(tasks) {
return tasks.reduce((p, task) => p.then(task), Promise.resolve())
}
Explicación
Si tiene estas tareas [t1, t2, t3]
, entonces lo anterior es equivalente a Promise.resolve().then(t1).then(t2).then(t3)
. Es el comportamiento de reducir.
Cómo utilizar
Primero, ¡necesitas construir una lista de tareas! Una tarea es una función que no acepta ningún argumento. Si necesita pasar argumentos a su función, utilice bind
u otros métodos para crear una tarea. Por ejemplo:
var tasks = files.map(file => processFile.bind(null, file))
inSequence(tasks).then(...)
Esta pregunta es antigua, pero vivimos en un mundo de ES6 y JavaScript funcional, así que veamos cómo podemos mejorar.
Debido a que las promesas se ejecutan inmediatamente, no podemos simplemente crear una serie de promesas, todas se dispararían en paralelo.
En cambio, necesitamos crear una serie de funciones que devuelva una promesa. Cada función se ejecutará secuencialmente, lo que luego inicia la promesa en el interior.
Podemos resolver esto de varias maneras, pero mi forma favorita es usar reduce
.
Se vuelve un poco complicado usar reduce
en combinación con las promesas, así que he desglosado el delineador en pequeñas picaduras más pequeñas que se pueden digerir a continuación.
La esencia de esta función es usar reduce
comenzando con un valor inicial de Promise.resolve([])
, o una promesa que contiene una matriz vacía.
Esta promesa se pasará al método reduce
como una promise
. Esta es la clave para encadenar cada promesa de forma secuencial. La próxima promesa de ejecución es func
y cuando el fuego then
dispara, los resultados se concatenan y esa promesa se devuelve, ejecutando el ciclo de reduce
con la siguiente función prometedora.
Una vez que se hayan ejecutado todas las promesas, la promesa devuelta contendrá una matriz de todos los resultados de cada promesa.
Ejemplo ES6 (un trazador de líneas)
/*
* serial executes Promises sequentially.
* @param {funcs} An array of funcs that return promises.
* @example
* const urls = [''/url1'', ''/url2'', ''/url3'']
* serial(urls.map(url => () => $.ajax(url)))
* .then(console.log.bind(console))
*/
const serial = funcs =>
funcs.reduce((promise, func) =>
promise.then(result => func().then(Array.prototype.concat.bind(result))), Promise.resolve([]))
Ejemplo ES6 (desglosado)
// broken down to for easier understanding
const concat = list => Array.prototype.concat.bind(list)
const promiseConcat = f => x => f().then(concat(x))
const promiseReduce = (acc, x) => acc.then(promiseConcat(x))
/*
* serial executes Promises sequentially.
* @param {funcs} An array of funcs that return promises.
* @example
* const urls = [''/url1'', ''/url2'', ''/url3'']
* serial(urls.map(url => () => $.ajax(url)))
* .then(console.log.bind(console))
*/
const serial = funcs => funcs.reduce(promiseReduce, Promise.resolve([]))
Uso:
// first take your work
const urls = [''/url1'', ''/url2'', ''/url3'', ''/url4'']
// next convert each item to a function that returns a promise
const funcs = urls.map(url => () => $.ajax(url))
// execute them serially
serial(funcs)
.then(console.log.bind(console))
Esto se extiende a cómo procesar una secuencia de promesas de una manera más genérica, soportando secuencias dinámicas / infinitas, basadas en la implementación de spex.sequence :
var $q = require("q");
var spex = require(''spex'')($q);
var files = []; // any dynamic source of files;
var readFile = function (file) {
// returns a promise;
};
function source(index) {
if (index < files.length) {
return readFile(files[index]);
}
}
function dest(index, data) {
// data = resolved data from readFile;
}
spex.sequence(source, dest)
.then(function (data) {
// finished the sequence;
})
.catch(function (error) {
// error;
});
No solo esta solución funcionará con secuencias de cualquier tamaño, sino que también puede agregarle fácilmente regulación de velocidad y equilibrio de carga .
La mejor solución que pude descifrar fue con las promesas de los bluebird
. Puede hacer Promise.resolve(files).each(fs.readFileAsync);
que garantiza que las promesas se resuelven secuencialmente en orden.
Mi respuesta está basada en https://.com/a/31070150/7542429 .
Promise.series = function series(arrayOfPromises) {
var results = [];
return arrayOfPromises.reduce(function(seriesPromise, promise) {
return seriesPromise.then(function() {
return promise
.then(function(result) {
results.push(result);
});
});
}, Promise.resolve())
.then(function() {
return results;
});
};
Esta solución devuelve los resultados como una matriz como Promise.all ().
Uso:
Promise.series([array of promises])
.then(function(results) {
// do stuff with results here
});
Mi solución preferida:
function processArray(arr, fn) {
return arr.reduce(
(p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
Promise.resolve([])
);
}
No es fundamentalmente diferente de otros publicados aquí, pero:
- Aplica la función a elementos en serie
- Resuelve una variedad de resultados
- No requiere async / await (el soporte es bastante limitado, alrededor de 2017)
- Utiliza funciones de flecha; agradable y conciso
Ejemplo de uso:
const numbers = [0, 4, 20, 100];
const multiplyBy3 = (x) => new Promise(res => res(x * 3));
// Prints [ 0, 12, 60, 300 ]
processArray(numbers, multiplyBy3).then(console.log);
Probado en corriente razonable Chrome (v59) y NodeJS (v8.1.2).
Para hacer esto simplemente en ES6:
function(files) {
// Create a new empty promise (don''t do that with real people ;)
var sequence = Promise.resolve();
// Loop over each file, and add on a promise to the
// end of the ''sequence'' promise.
files.forEach(function(file) {
// Chain one computation onto the sequence
sequence = sequence.then(function() {
return performComputation(file);
}).then(function(result) {
doSomething(result) // Resolves for each file, one at a time.
});
})
// This will resolve after the entire chain is resolved
return sequence;
}
Puede usar esta función que obtiene la lista de promiseFactories:
function executeSequentially(promiseFactories) {
var result = Promise.resolve();
promiseFactories.forEach(function (promiseFactory) {
result = result.then(promiseFactory);
});
return result;
}
Promise Factory es una función simple que devuelve una Promesa:
function myPromiseFactory() {
return somethingThatCreatesAPromise();
}
Funciona porque una fábrica de promesas no crea la promesa hasta que se le solicite. Funciona de la misma manera que una función en ese momento; de hecho, ¡es lo mismo!
No quiere operar sobre una serie de promesas. Según la especificación Promesa, tan pronto como se crea una promesa, comienza a ejecutarse. Entonces, lo que realmente quieres es una serie de fábricas prometedoras ...
Si desea obtener más información sobre Promises, debe consultar este enlace: pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
Realmente me gustó la respuesta de @ joelnet, pero para mí, ese estilo de codificación es un poco difícil de digerir, así que pasé un par de días tratando de descubrir cómo expresaría la misma solución de una manera más legible y este es mi tomar, solo con una sintaxis diferente y algunos comentarios.
// first take your work
const urls = [''/url1'', ''/url2'', ''/url3'', ''/url4'']
// next convert each item to a function that returns a promise
const functions = urls.map((url) => {
// For every url we return a new function
return () => {
return new Promise((resolve) => {
// random wait in milliseconds
const randomWait = parseInt((Math.random() * 1000),10)
console.log(''waiting to resolve in ms'', randomWait)
setTimeout(()=>resolve({randomWait, url}),randomWait)
})
}
})
const promiseReduce = (acc, next) => {
// we wait for the accumulator to resolve it''s promise
return acc.then((accResult) => {
// and then we return a new promise that will become
// the new value for the accumulator
return next().then((nextResult) => {
// that eventually will resolve to a new array containing
// the value of the two promises
return accResult.concat(nextResult)
})
})
};
// the accumulator will always be a promise that resolves to an array
const accumulator = Promise.resolve([])
// we call reduce with the reduce function and the accumulator initial value
functions.reduce(promiseReduce, accumulator)
.then((result) => {
// let''s display the final value here
console.log(''=== The final result ==='')
console.log(result)
})
Si lo desea, puede usar reducir para hacer una promesa secuencial, por ejemplo:
[2,3,4,5,6,7,8,9].reduce((promises, page) => {
return promises.then((page) => {
console.log(page);
return Promise.resolve(page+1);
});
}, Promise.resolve(1));
siempre funcionará en secuencia.
Sobre la base del título de la pregunta, "Resolver promete una tras otra (es decir, en secuencia)?", Podríamos entender que la OP está más interesada en el manejo secuencial de las promesas de liquidación que las llamadas secuenciales per se .
Esta respuesta se ofrece:
- para demostrar que las llamadas secuenciales no son necesarias para el manejo secuencial de las respuestas.
- para exponer patrones alternativos viables a los visitantes de esta página, incluido el OP si todavía está interesado más de un año después.
- a pesar de la afirmación de OP de que no desea hacer llamadas concurrentemente, lo que puede ser genuino, pero igualmente puede ser una suposición basada en el deseo de manejo secuencial de las respuestas como lo implica el título.
Si las llamadas simultáneas no son realmente deseadas, entonces vea la respuesta de Benjamin Gruenbaum que cubre llamadas secuenciales (etc.) de manera exhaustiva.
Sin embargo, si está interesado (para un mejor rendimiento) en patrones que permiten llamadas concurrentes seguidas de un manejo secuencial de las respuestas, continúe leyendo.
Es tentador pensar que tienes que usar Promise.all(arr.map(fn)).then(fn)
(como lo he hecho muchas veces) o un azúcar de fantasía de Promise lib (notablemente Bluebird), sin embargo (con crédito para este artículo) ) un arr.map(fn).reduce(fn)
hará el trabajo, con las ventajas de que:
- funciona con cualquier promesa lib - incluso versiones
.then()
de jQuery - solo se usa.then()
. - ofrece la flexibilidad de saltar por error o detener-en-error, lo que quieras con un mod de una línea.
Aquí está, escrito para Q
var readFiles = function(files) {
return files.map(readFile) //Make calls in parallel.
.reduce(function(sequence, filePromise) {
return sequence.then(function() {
return filePromise;
}).then(function(file) {
//Do stuff with file ... in the correct sequence!
}, function(error) {
console.log(error); //optional
return sequence;//skip-over-error. To stop-on-error, `return error` (jQuery), or `throw error` (Promises/A+).
});
}, Q()).then(function() {
// all done.
});
};
Nota: solo ese fragmento, Q()
, es específico de Q. Para jQuery debe asegurarse de que readFile () devuelva una promesa jQuery. Con A + libs, las promesas extranjeras serán asimiladas.
La clave aquí es la promesa de sequence
de reducción, que secuencia el manejo de las promesas de readFile
pero no su creación.
Y una vez que hayas absorbido eso, tal vez sea un poco alucinante cuando te .map()
cuenta de que el escenario .map()
no es realmente necesario. Todo el trabajo, las llamadas en paralelo y el manejo en serie en el orden correcto, se puede lograr con reduce()
solo, además de la ventaja adicional de mayor flexibilidad para:
- Convierta de llamadas asincrónicas paralelas a llamadas asincrónicas en serie simplemente moviendo una línea, potencialmente útil durante el desarrollo.
Aquí está, para Q
nuevamente.
var readFiles = function(files) {
return files.reduce(function(sequence, f) {
var filePromise = readFile(f);//Make calls in parallel. To call sequentially, move this line down one.
return sequence.then(function() {
return filePromise;
}).then(function(file) {
//Do stuff with file ... in the correct sequence!
}, function(error) {
console.log(error); //optional
return sequence;//Skip over any errors. To stop-on-error, `return error` (jQuery), or `throw error` (Promises/A+).
});
}, Q()).then(function() {
// all done.
});
};
Ese es el patrón básico. Si también desea entregar datos (por ejemplo, los archivos o alguna transformación de ellos) a la persona que llama, necesitaría una variante leve.
Su enfoque no es malo, pero tiene dos problemas: se traga errores y emplea el Antipatín de construcción de promesas explícitas.
Puede resolver estos dos problemas y hacer que el código sea más limpio, al tiempo que sigue empleando la misma estrategia general:
var Q = require("q");
var readFile = function(file) {
... // Returns a promise.
};
var readFiles = function(files) {
var readSequential = function(index) {
if (index < files.length) {
return readFile(files[index]).then(function() {
return readSequential(index + 1);
});
}
};
// using Promise.resolve() here in case files.length is 0
return Promise.resolve(readSequential(0)); // Start!
};
Tuve que ejecutar muchas tareas secuenciales y usé estas respuestas para forjar una función que se encargaría de manejar cualquier tarea secuencial ...
function one_by_one(objects_array, iterator, callback) {
var start_promise = objects_array.reduce(function (prom, object) {
return prom.then(function () {
return iterator(object);
});
}, Promise.resolve()); // initial
if(callback){
start_promise.then(callback);
}else{
return start_promise;
}
}
La función toma 2 argumentos + 1 opcional. El primer argumento es la matriz en la que trabajaremos. El segundo argumento es la tarea en sí, una función que devuelve una promesa, la siguiente tarea se iniciará solo cuando se resuelva esta promesa. El tercer argumento es una devolución de llamada para ejecutar cuando se hayan realizado todas las tareas. Si no se pasa una devolución de llamada, la función devuelve la promesa que creó para que podamos manejar el final.
Aquí hay un ejemplo de uso:
var filenames = [''1.jpg'',''2.jpg'',''3.jpg''];
var resize_task = function(filename){
//return promise of async resizing with filename
};
one_by_one(filenames,resize_task );
Espero que le ahorre a alguien algo de tiempo ...
Uso el siguiente código para extender el objeto Promesa. Maneja el rechazo de las promesas y devuelve una variedad de resultados
Código
/*
Runs tasks in sequence and resolves a promise upon finish
tasks: an array of functions that return a promise upon call.
parameters: an array of arrays corresponding to the parameters to be passed on each function call.
context: Object to use as context to call each function. (The ''this'' keyword that may be used inside the function definition)
*/
Promise.sequence = function(tasks, parameters = [], context = null) {
return new Promise((resolve, reject)=>{
var nextTask = tasks.splice(0,1)[0].apply(context, parameters[0]); //Dequeue and call the first task
var output = new Array(tasks.length + 1);
var errorFlag = false;
tasks.forEach((task, index) => {
nextTask = nextTask.then(r => {
output[index] = r;
return task.apply(context, parameters[index+1]);
}, e=>{
output[index] = e;
errorFlag = true;
return task.apply(context, parameters[index+1]);
});
});
// Last task
nextTask.then(r=>{
output[output.length - 1] = r;
if (errorFlag) reject(output); else resolve(output);
})
.catch(e=>{
output[output.length - 1] = e;
reject(output);
});
});
};
Ejemplo
function functionThatReturnsAPromise(n) {
return new Promise((resolve, reject)=>{
//Emulating real life delays, like a web request
setTimeout(()=>{
resolve(n);
}, 1000);
});
}
var arrayOfArguments = [[''a''],[''b''],[''c''],[''d'']];
var arrayOfFunctions = (new Array(4)).fill(functionThatReturnsAPromise);
Promise.sequence(arrayOfFunctions, arrayOfArguments)
.then(console.log)
.catch(console.error);
Utilice Array.prototype.reduce
, y recuerde cumplir sus promesas en una función; de lo contrario, ya se estarán ejecutando.
// array of Promise providers
const providers = [
function(){
return Promise.resolve(1);
},
function(){
return Promise.resolve(2);
},
function(){
return Promise.resolve(3);
}
]
const seed = Promise.resolve(null);
const inSeries = function(providers){
return providers.reduce(function(a,b){
return a.then(b);
}, seed);
};
agradable y fácil ... deberías poder volver a utilizar la misma semilla para el rendimiento, etc.
Utilización simple para la promesa estándar de Node.js:
function sequence(tasks, fn) {
return tasks.reduce((promise, task) => promise.then(() => fn(task)), Promise.resolve());
}
ACTUALIZAR
items-promise es un paquete de NPM listo para usar que hace lo mismo.
Actualización 2017 : Utilizaría una función asíncrona si el entorno lo admite:
async function readFiles(files) {
for(const file of files) {
await readFile(file);
}
};
Si lo desea, puede diferir la lectura de los archivos hasta que los necesite utilizando un generador asíncrono (si su entorno lo admite):
async function* readFiles(files) {
for(const file of files) {
yield await readFile(file);
}
};
Actualización: Pensándolo bien: podría usar un bucle for en su lugar:
var readFiles = function(files) {
var p = Q(); // Promise.resolve() without Q
files.forEach(function(file){
p = p.then(function(){ return readFile(file); }); // or .bind
});
return p;
};
O más compacto, con reducir:
var readFiles = function(files) {
return files.reduce(function(p, file) {
return p.then(function(){ return readFile(file); });
},Q()); // initial
};
En otras bibliotecas de promesas (como cuándo y Bluebird) tienes métodos de utilidad para esto.
Por ejemplo, Bluebird sería:
var Promise = require("bluebird");
var fs = Promise.promisifyAll(require("fs"));
var readAll = Promise.resolve(files).map(fs.readFileAsync,{concurrency: 1 });
// if the order matters, you can use Promise.each instead and omit concurrency param
readAll.then(function(allFileContents){
// do stuff to read files.
});
En Q, lo que tienes es lo mejor que puedes obtener: puedes acortarlo con Array.prototype.reduce
un poco y extraerlo en un método genérico.
Si puede usar Q.async
(es decir, está en el nodo) las cosas mejoran:
Q.spawn(function* () {
var results = [];
for(var i = 0;i < files.length; i++){
results.push(yield readFile(files[i]));
}
console.log(results);
});
Solo recuerde ejecutar el nodo con --harmony
y recuerde que es atm experimental .