Considérez le code suivant qui lit un tableau de fichiers de manière série/séquentielle. readFiles
renvoie une promesse, qui n'est résolue que lorsque tous les fichiers ont été lus dans l'ordre.
var readFile = function(file) {
... // Returns a promise.
};
var readFiles = function(files) {
return new Promise((resolve, reject) =>
var readSequential = function(index) {
if (index >= files.length) {
resolve();
} else {
readFile(files[index]).then(function() {
readSequential(index + 1);
}).catch(reject);
}
};
readSequential(0); // Start!
});
};
Le code ci-dessus fonctionne, mais je n'aime pas avoir à faire de la récursivité pour que les choses se produisent de manière séquentielle. Existe-t-il un moyen plus simple de réécrire ce code afin de ne pas utiliser ma fonction bizarre readSequential
?
A l'origine, j'ai essayé d'utiliser Promise.all
, mais cela a provoqué tous les appels readFile
en même temps, ce qui est pas ce que je veux:
var readFiles = function(files) {
return Promise.all(files.map(function(file) {
return readFile(file);
}));
};
Update 2017 : J'utiliserais une fonction asynchrone si l'environnement le permet:
async function readFiles(files) {
for(const file of files) {
await readFile(file);
}
};
Si vous le souhaitez, vous pouvez différer la lecture des fichiers jusqu'à ce que vous en ayez besoin à l'aide d'un générateur async (si votre environnement le prend en charge):
async function* readFiles(files) {
for(const file of files) {
yield await readFile(file);
}
};
Mise à jour: À bien y penser, je pourrais utiliser une boucle for à la place:
var readFiles = function(files) {
var p = Promise.resolve(); // Q() in q
files.forEach(file =>
p = p.then(() => readFile(file));
);
return p;
};
Ou plus compactement, avec réduire:
var readFiles = function(files) {
return files.reduce((p, file) => {
return p.then(() => readFile(file));
}, Promise.resolve()); // initial
};
Dans d'autres bibliothèques de promesses (comme When et Bluebird), vous disposez de méthodes utilitaires pour cela.
Par exemple, Bluebird serait:
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.
});
Bien qu'il n'y ait vraiment aucune raison not d'utiliser l'attente asynchrone aujourd'hui.
Voici comment je préfère exécuter des tâches en série .
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'en est-il des cas avec plus de tâches? Comme, 10?
function runSerial(tasks) {
var result = Promise.resolve();
tasks.forEach(task => {
result = result.then(() => task());
});
return result;
}
Cette question est ancienne, mais nous vivons dans un monde d'ES6 et de JavaScript fonctionnel, voyons comment nous pouvons nous améliorer.
Comme les promesses sont exécutées immédiatement, nous ne pouvons pas simplement créer un tableau de promesses, elles se déclencheraient toutes en parallèle.
Au lieu de cela, nous devons créer un tableau de fonctions qui renvoie une promesse. Chaque fonction sera ensuite exécutée séquentiellement, ce qui lancera la promesse à l'intérieur.
Nous pouvons résoudre ce problème de plusieurs manières, mais ma méthode préférée consiste à utiliser reduce
.
Cela devient un peu délicat d’utiliser reduce
en combinaison avec des promesses, c’est pourquoi j’ai décomposé la doublure en plusieurs petites bouchées digestibles ci-dessous.
L'essence de cette fonction consiste à utiliser reduce
à partir d'une valeur initiale de Promise.resolve([])
ou d'une promesse contenant un tableau vide.
Cette promesse sera ensuite transmise à la méthode reduce
en tant que promise
. C’est la clé pour enchaîner chaque promesse les unes après les autres. La prochaine promesse à exécuter est func
et lorsque la then
est déclenchée, les résultats sont concaténés et cette promesse est ensuite renvoyée, en exécutant le cycle reduce
avec la fonction promesse suivante.
Une fois que toutes les promesses ont été exécutées, la promesse retournée contiendra un tableau de tous les résultats de chaque promesse.
ES6 Exemple (une doublure)
/*
* 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([]))
ES6 Exemple (décomposé)
// 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([]))
Usage:
// 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))
Pour le faire simplement dans 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;
}
Utilisation simple pour la promesse standard de Node.js:
function sequence(tasks, fn) {
return tasks.reduce((promise, task) => promise.then(() => fn(task)), Promise.resolve());
}
METTRE À JOUR
items-promise est un package NPM prêt à l'emploi faisant de même.
J'ai dû exécuter beaucoup de tâches séquentielles et j'ai utilisé ces réponses pour créer une fonction permettant de gérer toute tâche séquentielle ...
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 fonction prend 2 arguments + 1 optionnel. Le premier argument est le tableau sur lequel nous allons travailler. Le deuxième argument est la tâche elle-même, une fonction qui renvoie une promesse, la tâche suivante ne sera lancée que lorsque cette promesse sera résolue. Le troisième argument est un rappel à exécuter lorsque toutes les tâches ont été effectuées. Si aucun rappel n'est passé, la fonction retourne la promesse créée afin que nous puissions gérer la fin.
Voici un exemple d'utilisation:
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 );
J'espère que ça fait gagner du temps à quelqu'un ...
Ceci est une légère variation d'une autre réponse ci-dessus. Utilisation de promesses natives:
function inSequence(tasks) {
return tasks.reduce((p, task) => p.then(task), Promise.resolve())
}
Explication
Si vous avez ces tâches [t1, t2, t3]
, alors ce qui précède équivaut à Promise.resolve().then(t1).then(t2).then(t3)
. C'est le comportement de réduire.
Comment utiliser
First Vous devez construire une liste de tâches! Une tâche est une fonction qui n'accepte aucun argument. Si vous devez passer des arguments à votre fonction, utilisez bind
ou d'autres méthodes pour créer une tâche. Par exemple:
var tasks = files.map(file => processFile.bind(null, file))
inSequence(tasks).then(...)
Ma solution préférée:
function processArray(arr, fn) {
return arr.reduce(
(p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
Promise.resolve([])
);
}
Ce n'est pas fondamentalement différent des autres publiés ici, mais:
Exemple d'utilisation:
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);
Testé sur les moteurs Chrome (v59) et NodeJS (v8.1.2).
La meilleure solution que j'ai pu trouver était avec des promesses bluebird
. Vous pouvez simplement faire Promise.resolve(files).each(fs.readFileAsync);
qui garantit que les promesses sont résolues de manière séquentielle dans l'ordre.
Utilisez Array.prototype.reduce
et n'oubliez pas de mettre vos promesses dans une fonction, sinon elles seront déjà exécutées!
// 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);
};
Sympa et facile .... vous devriez pouvoir réutiliser la même graine pour la performance, etc.
J'ai créé cette méthode simple sur l'objet 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();
});
};
var todo = [];
todo.Push(firstPromise);
if (someCriterium) todo.Push(optionalPromise);
todo.Push(lastPromise);
// Invoking them
Promise.sequence(todo)
.then(function(results) {}, function(results) {});
La meilleure chose à propos de cette extension à l'objet Promise est qu'elle est cohérente avec le style des promesses. Promise.all et Promise.sequence sont invoquées de la même manière, mais ont une sémantique différente.
L'exécution séquentielle des promesses n'est généralement pas un très bon moyen de les utiliser. Il est généralement préférable d’utiliser Promise.all et de laisser le navigateur exécuter le code aussi rapidement que possible. Cependant, il existe de réels cas d'utilisation - par exemple, lors de l'écriture d'une application mobile à l'aide de javascript.
J'ai bien aimé la réponse de @ joelnet, mais pour moi, ce style de codage est un peu difficile à digérer. J'ai donc passé quelques jours à essayer de comprendre comment exprimer la même solution de manière plus lisible. prendre, juste avec une syntaxe différente et quelques commentaires.
// 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)
})
Vous pouvez utiliser cette fonction qui obtient promiseFactories List:
function executeSequentially(promiseFactories) {
var result = Promise.resolve();
promiseFactories.forEach(function (promiseFactory) {
result = result.then(promiseFactory);
});
return result;
}
Promise Factory est une simple fonction qui renvoie une promesse:
function myPromiseFactory() {
return somethingThatCreatesAPromise();
}
Cela fonctionne car une fabrique de promesses ne crée pas la promesse tant qu'elle n'est pas sollicitée. Cela fonctionne de la même manière qu'une fonction d'alors - en fait, c'est la même chose!
Vous ne voulez pas accomplir une foule de promesses. Selon les spécifications de la promesse, dès qu'une promesse est créée, elle commence à être exécutée. Donc, ce que vous voulez vraiment, c'est un ensemble d'usines de promesses ...
Si vous voulez en savoir plus sur Promises, vous devriez vérifier ce lien: https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
J'utilise le code suivant pour étendre l'objet Promise. Il gère le rejet des promesses et renvoie un tableau de résultats
Code
/*
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);
});
});
};
Exemple
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);
Si vous le souhaitez, vous pouvez utiliser réduire pour faire une promesse séquentielle, par exemple:
[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));
ça marche toujours en séquentiel.
Comme Bergi l’a remarqué, je pense que la meilleure solution, c’est d’utiliser BlueBird.each, code ci-dessous:
const BlueBird = require('bluebird');
BlueBird.each(files, fs.readFileAsync);
Ma réponse basée sur https://stackoverflow.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;
});
};
Cette solution renvoie les résultats sous forme de tableau comme Promise.all ().
Usage:
Promise.series([array of promises])
.then(function(results) {
// do stuff with results here
});
La méthode Array Push and Pop peut être utilisée pour une séquence de promesses. Vous pouvez également envoyer de nouvelles promesses lorsque vous avez besoin de données supplémentaires. C’est le code que je vais utiliser dans le chargeur React Infinite pour charger une séquence de pages.
var promises = [Promise.resolve()];
function methodThatReturnsAPromise(page) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`Resolve-${page}! ${new Date()} `);
resolve();
}, 1000);
});
}
function pushPromise(page) {
promises.Push(promises.pop().then(function () {
return methodThatReturnsAPromise(page)
}));
}
pushPromise(1);
pushPromise(2);
pushPromise(3);
Je ne comprends pas pourquoi les gens proposent des solutions aussi complexes. Voici une logique plus simple
function downloadFile(fileUrl) { ... } // This function return a Promise
async function main()
{
var filesList = [...];
for (var i = 0; i <= filesList.length; i++)
{
await downloadFile(filesList[i]);
}
}
function downloadFile(fileUrl) { ... } // This function return a Promise
function downloadRecursion(filesList, index)
{
if (index < filesList.length)
{
downloadFile(filesList[index]).then(function()
{
index++;
downloadRecursion(filesList, index); // self invocation - recursion!
});
}
else
{
return Promise.resolve();
}
}
function main()
{
var filesList = [...];
downloadFile(filesList, 0);
}
Si quelqu'un d'autre a besoin d'un moyen garanti de manière STRICTEMENT séquentielle de résoudre les promesses lors de l'exécution d'opérations CRUD, vous pouvez également utiliser le code suivant comme base.
Tant que vous ajoutez 'return' avant d'appeler chaque fonction, décrivant une promesse, et utilisez cet exemple comme base, l'appel de fonction suivant .then () commencera CONSISTANTEMENT après l'achèvement de la précédente:
getRidOfOlderShoutsPromise = () => {
return readShoutsPromise('BEFORE')
.then(() => {
return deleteOlderShoutsPromise();
})
.then(() => {
return readShoutsPromise('AFTER')
})
.catch(err => console.log(err.message));
}
deleteOlderShoutsPromise = () => {
return new Promise ( (resolve, reject) => {
console.log("in deleteOlderShouts");
let d = new Date();
let TwoMinuteAgo = d - 1000 * 90 ;
All_Shouts.deleteMany({ dateTime: {$lt: TwoMinuteAgo}}, function(err) {
if (err) reject();
console.log("DELETED OLDs at "+d);
resolve();
});
});
}
readShoutsPromise = (tex) => {
return new Promise( (resolve, reject) => {
console.log("in readShoutsPromise -"+tex);
All_Shouts
.find({})
.sort([['dateTime', 'ascending']])
.exec(function (err, data){
if (err) reject();
let d = new Date();
console.log("shouts "+tex+" delete PROMISE = "+data.length +"; date ="+d);
resolve(data);
});
});
}
Beaucoup de réponses ici mais je n'ai pas vu cette solution simple:
await array.reduce(
async (promise, member) => await myLongSequentialPromise(member),
array[0]
)
Sur la base du titre de la question, "Résoudre les promesses les unes après les autres (en séquence)?", Nous pourrions comprendre que le PO s'intéresse davantage au traitement séquentiel des promesses au règlement que des appels séquentiels per se.
Cette réponse est offerte:
Si les appels simultanés ne sont vraiment pas souhaités, consultez la réponse de Benjamin Gruenbaum qui traite de manière exhaustive des appels séquentiels (etc.).
Si toutefois, vous êtes intéressé (pour améliorer les performances) par des modèles autorisant les appels simultanés suivis du traitement séquentiel des réponses, veuillez lire la suite.
Il est tentant de penser que vous devez utiliser Promise.all(arr.map(fn)).then(fn)
(comme je l'ai souvent fait) ou le sucre fantaisie de Promise lib (notamment Bluebird). Cependant (avec le mérite de cet article ) un modèle arr.map(fn).reduce(fn)
fera l'affaire, avec le paramètre avantages qu'il:
.then()
est utilisée.La voici écrite pour 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.
});
};
Remarque: seul ce fragment, Q()
, est spécifique à Q. Pour jQuery, vous devez vous assurer que readFile () renvoie une promesse jQuery. Avec A + libs, les promesses étrangères seront assimilées.
La clé ici est la promesse sequence
de la réduction, qui séquence les manipulations des promesses readFile
mais pas leur création.
Et une fois que vous avez assimilé cela, c'est peut-être un peu hallucinant de comprendre que l'étape .map()
n'est pas réellement nécessaire! L'ensemble de la tâche, les appels parallèles et la gestion en série dans le bon ordre, peut être réalisé avec reduce()
seul, avec l'avantage supplémentaire d'une plus grande flexibilité pour:
Le voici, pour Q
encore.
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.
});
};
C'est le modèle de base. Si vous souhaitez également transmettre des données (par exemple, les fichiers ou une transformation de celles-ci) à l'appelant, vous avez besoin d'une variante douce.
Votre approche n’est pas mauvaise, mais elle a deux problèmes: elle avalise les erreurs et utilise l’antipattern de Construction de la promesse explicite.
Vous pouvez résoudre ces deux problèmes et rendre le code plus propre, tout en appliquant la même stratégie générale:
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!
};