Betrachten Sie den folgenden Code, der ein Array von Dateien seriell/sequentiell liest. readFiles
gibt ein Versprechen zurück, das erst aufgelöst wird, wenn alle Dateien der Reihe nach gelesen wurden.
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!
});
};
Der obige Code funktioniert, aber ich mag keine Rekursion, damit die Dinge nacheinander auftreten. Gibt es eine einfachere Möglichkeit, diesen Code umzuschreiben, damit ich meine komische readSequential
-Funktion nicht verwenden muss?
Ursprünglich habe ich versucht, Promise.all
zu verwenden, aber das führte dazu, dass alle readFile
-Aufrufe gleichzeitig ablaufen, was nicht was ich will:
var readFiles = function(files) {
return Promise.all(files.map(function(file) {
return readFile(file);
}));
};
Update 2017 : Ich würde eine asynchrone Funktion verwenden, wenn die Umgebung dies unterstützt:
async function readFiles(files) {
for(const file of files) {
await readFile(file);
}
};
Wenn Sie möchten, können Sie das Lesen der Dateien verzögern, bis Sie sie mit einem Asynchrongenerator benötigen (sofern Ihre Umgebung dies unterstützt):
async function* readFiles(files) {
for(const file of files) {
yield await readFile(file);
}
};
Update: Im zweiten Gedanken - ich könnte stattdessen eine for-Schleife verwenden:
var readFiles = function(files) {
var p = Promise.resolve(); // Q() in q
files.forEach(file =>
p = p.then(() => readFile(file));
);
return p;
};
Oder kompakter mit reduzierten:
var readFiles = function(files) {
return files.reduce((p, file) => {
return p.then(() => readFile(file));
}, Promise.resolve()); // initial
};
In anderen Versprechenbibliotheken (z. B. wann und Bluebird) gibt es hierfür Methoden.
Bluebird wäre zum Beispiel:
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.
});
Zwar gibt es wirklich keinen Grund, not, async heute zu verwenden.
So führe ich es vor, Aufgaben in Serie auszuführen.
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 ----");
});
}
Was ist mit Fällen mit mehr Aufgaben? Wie 10?
function runSerial(tasks) {
var result = Promise.resolve();
tasks.forEach(task => {
result = result.then(() => task());
});
return result;
}
Diese Frage ist alt, aber wir leben in einer Welt von ES6 und funktionalem JavaScript. Sehen wir uns also an, wie wir uns verbessern können.
Da Versprechen sofort ausgeführt werden, können wir nicht nur eine Reihe von Versprechen erstellen, sie würden alle gleichzeitig abfeuern.
Stattdessen müssen wir ein Array von Funktionen erstellen, das ein Versprechen zurückgibt. Jede Funktion wird dann nacheinander ausgeführt, wodurch das Versprechen darin beginnt.
Wir können das auf ein paar Arten lösen, aber am liebsten nutze ich reduce
.
Mit reduce
in Kombination mit Versprechungen wird es etwas kniffelig, daher habe ich den einen Liner in einige kleinere, leicht verdauliche Bisse zerlegt.
Die Essenz dieser Funktion besteht darin, reduce
zu verwenden, beginnend mit einem Anfangswert von Promise.resolve([])
oder einem Versprechen, das ein leeres Array enthält.
Dieses Versprechen wird dann als reduce
an die promise
-Methode übergeben. Dies ist der Schlüssel zur Verkettung jedes Versprechens nacheinander. Das nächste auszuführende Versprechen ist func
. Wenn die then
ausgelöst wird, werden die Ergebnisse verkettet und dieses Versprechen wird zurückgegeben, wobei der reduce
-Zyklus mit der nächsten Versprechungsfunktion ausgeführt wird.
Sobald alle Versprechen ausgeführt wurden, enthält das zurückgegebene Versprechen ein Array aller Ergebnisse jedes Versprechens.
ES6-Beispiel (ein Liner)
/*
* 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-Beispiel (aufgeschlüsselt)
// 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([]))
Verwendungszweck:
// 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))
Dazu einfach in 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;
}
Einfache Verwendung für das Standardversprechen von Node.js:
function sequence(tasks, fn) {
return tasks.reduce((promise, task) => promise.then(() => fn(task)), Promise.resolve());
}
UPDATE
Einzelteile-Versprechen ist ein gebrauchsfertiges NPM-Paket, das dasselbe tut.
Ich musste viele sequenzielle Aufgaben ausführen und diese Antworten verwenden, um eine Funktion zu erstellen, die sich um die Bearbeitung aller sequenziellen Aufgaben kümmert ...
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;
}
}
Die Funktion akzeptiert 2 Argumente + 1 optional. Das erste Argument ist das Array, an dem wir arbeiten werden. Das zweite Argument ist die Task selbst, eine Funktion, die ein Versprechen zurückgibt. Die nächste Task wird nur gestartet, wenn dieses Versprechen aufgelöst wird. Das dritte Argument ist ein Rückruf, der ausgeführt wird, wenn alle Aufgaben ausgeführt wurden. Wenn kein Rückruf übergeben wird, gibt die Funktion das erstellte Versprechen zurück, damit wir das Ende abwickeln können.
Hier ist ein Beispiel für die Verwendung:
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 );
Hoffe es erspart jemandem etwas Zeit ...
Dies ist eine geringfügige Abweichung von einer anderen Antwort oben. Native Versprechen verwenden:
function inSequence(tasks) {
return tasks.reduce((p, task) => p.then(task), Promise.resolve())
}
Erklärung
Wenn Sie diese Aufgaben [t1, t2, t3]
haben, entspricht das Obige Promise.resolve().then(t1).then(t2).then(t3)
. Es ist das Verhalten von reduzieren.
Wie benutzt man
First Sie müssen eine Aufgabenliste erstellen! Eine Aufgabe ist eine Funktion, die kein Argument akzeptiert. Wenn Sie Ihrer Funktion Argumente übergeben müssen, verwenden Sie bind
oder andere Methoden, um eine Aufgabe zu erstellen. Zum Beispiel:
var tasks = files.map(file => processFile.bind(null, file))
inSequence(tasks).then(...)
Meine bevorzugte Lösung:
function processArray(arr, fn) {
return arr.reduce(
(p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
Promise.resolve([])
);
}
Es unterscheidet sich nicht grundlegend von anderen hier veröffentlichten, aber:
Verwendungsbeispiel:
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);
Getestet auf vernünftigen aktuellen Chrome (v59) und NodeJS (v8.1.2).
Die schönste Lösung, die ich herausfinden konnte, war mit bluebird
Versprechen. Sie können einfach Promise.resolve(files).each(fs.readFileAsync);
tun, was garantiert, dass die Versprechen der Reihe nach in der Reihenfolge aufgelöst werden.
Verwenden Sie Array.prototype.reduce
und denken Sie daran, Ihre Versprechen in eine Funktion zu packen, da sie sonst bereits ausgeführt werden!
// 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);
};
Schön und einfach ... Sie sollten in der Lage sein, denselben Samen für die Leistung usw. erneut zu verwenden.
Ich habe diese einfache Methode für das Promise-Objekt erstellt:
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) {});
Das Beste an dieser Erweiterung des Promise-Objekts ist, dass es dem Stil der Versprechen entspricht. Promise.all und Promise.sequence werden auf dieselbe Weise aufgerufen, haben jedoch unterschiedliche Semantiken.
Die sequentielle Abwicklung von Versprechen ist in der Regel keine sehr gute Möglichkeit, Versprechungen zu verwenden. Es ist normalerweise besser, Promise.all zu verwenden, und den Browser den Code so schnell wie möglich ausführen zu lassen. Es gibt jedoch echte Anwendungsfälle - zum Beispiel beim Schreiben einer mobilen App mit Javascript.
@ Joelnets Antwort hat mir sehr gefallen, aber für mich ist diese Art der Codierung etwas schwer zu verstehen, also habe ich ein paar Tage damit verbracht, herauszufinden, wie ich dieselbe Lösung lesbarer formulieren würde und dies ist meine Nehmen Sie, nur mit einer anderen Syntax und einigen Kommentaren.
// 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)
})
Sie können diese Funktion verwenden, um die Liste promiseFactories abzurufen:
function executeSequentially(promiseFactories) {
var result = Promise.resolve();
promiseFactories.forEach(function (promiseFactory) {
result = result.then(promiseFactory);
});
return result;
}
Promise Factory ist nur eine einfache Funktion, die ein Versprechen zurückgibt:
function myPromiseFactory() {
return somethingThatCreatesAPromise();
}
Es funktioniert, weil eine Versprechenfabrik das Versprechen nicht schafft, bis es dazu aufgefordert wird. Es funktioniert genauso wie eine dann-Funktion - tatsächlich ist es das Gleiche!
Sie möchten nicht mit einer ganzen Reihe von Versprechen arbeiten. Nach der Versprechenspezifikation beginnt die Ausführung, sobald ein Versprechen erstellt wird. Was Sie wirklich wollen, ist eine Reihe von Versprechungsfabriken ...
Wenn Sie mehr über Versprechen erfahren möchten, sollten Sie diesen Link überprüfen: https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
Ich verwende den folgenden Code, um das Promise-Objekt zu erweitern. Es behandelt die Ablehnung der Versprechen und gibt eine Reihe von Ergebnissen zurück
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);
});
});
};
Beispiel
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);
Wenn Sie möchten, können Sie die Verwendung von verkleinern, um ein sequentielles Versprechen zu machen.
[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));
es funktioniert immer in sequentieller Reihenfolge.
Wie Bergi bemerkt hat, denke ich, ist die beste und klarste Lösung BlueBird.each, Code unten:
const BlueBird = require('bluebird');
BlueBird.each(files, fs.readFileAsync);
Meine Antwort basiert auf 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;
});
};
Diese Lösung gibt die Ergebnisse als Array wie Promise.all () zurück.
Verwendungszweck:
Promise.series([array of promises])
.then(function(results) {
// do stuff with results here
});
Die Array-Push- und -Pop-Methode kann für die Abfolge von Versprechungen verwendet werden. Sie können auch neue Versprechen pushen, wenn Sie zusätzliche Daten benötigen. Dies ist der Code, den ich in React Infinite Loader verwenden werde, um eine Folge von Seiten zu laden.
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);
Ich verstehe nicht, warum die Leute so komplexe Lösungen vorschlagen. Hier ist eine einfachere Logik
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);
}
Wenn eine andere Person einen garantierten Weg zur STRICTLY sequentiellen Lösung des Versprechens von CRUD-Vorgängen benötigt, können Sie auch den folgenden Code als Grundlage verwenden.
Solange Sie vor dem Aufruf jeder Funktion 'return' hinzufügen und ein Promise beschreiben, und dieses Beispiel als Grundlage verwenden, wird der nächste Aufruf der .then () - Funktion NACH dem Abschluss des vorherigen konsistent gestartet:
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);
});
});
}
Viele Antworten hier, aber ich habe diese einfache Lösung nicht gesehen:
await array.reduce(
async (promise, member) => await myLongSequentialPromise(member),
array[0]
)
Auf der Grundlage des Titels der Frage, "Versprechen nacheinander lösen (d. H. In Folge)?", Könnten wir verstehen, dass das OP mehr an der sequentiellen Behandlung von Versprechen bei Abwicklung interessiert ist als sequenzielle Aufrufe per se.
Diese Antwort wird angeboten:
Wenn gleichzeitige Anrufe wirklich nicht erwünscht sind, lesen Sie die Antwort von Benjamin Gruenbaum, die alle sequenziellen Anrufe (usw.) ausführlich behandelt.
Wenn Sie jedoch (für eine verbesserte Leistung) an Mustern interessiert sind, die gleichzeitige Anrufe gefolgt von einer sequentiellen Behandlung von Antworten zulassen, lesen Sie bitte weiter.
Es ist verlockend zu glauben, dass Sie Promise.all(arr.map(fn)).then(fn)
(wie ich es schon oft getan habe) oder einen ausgefallenen Zucker von Promise lib (insbesondere Bluebirds) verwenden müssen, jedoch (mit einem Verdienst von diesem Artikel ) ein arr.map(fn).reduce(fn)
-Muster die Aufgabe erledigen wird Vorteile, dass es:
.then()
verwendet.Hier ist es für Q
geschrieben.
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.
});
};
Hinweis: Nur dieses eine Fragment, Q()
, ist spezifisch für Q. Für jQuery müssen Sie sicherstellen, dass readFile () ein jQuery-Versprechen zurückgibt. Mit A + libs werden ausländische Versprechungen assimiliert.
Der Schlüssel hier ist das sequence
-Versprechen der Reduktion, das die Handhabung der readFile
verspricht, aber nicht ihre Erstellung.
Und wenn Sie das einmal in sich aufgenommen haben, ist es vielleicht etwas umwerfend, wenn Sie feststellen, dass die Phase .map()
nicht unbedingt notwendig ist! Der gesamte Job, parallele Abrufe und serielle Abwicklung in der richtigen Reihenfolge, kann nur mit reduce()
und zusätzlichem Vorteil der weiteren Flexibilität erreicht werden:
Hier ist es wieder für Q
.
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.
});
};
Das ist das Grundmuster. Wenn Sie dem Anrufer auch Daten übermitteln möchten (z. B. die Dateien oder eine Umwandlung davon), benötigen Sie eine einfache Variante.
Ihr Ansatz ist nicht schlecht, hat aber zwei Probleme: Er schluckt Fehler und verwendet das Explicit Promise Construction Antipattern.
Sie können beide Probleme lösen und den Code sauberer machen, wobei Sie immer noch dieselbe allgemeine Strategie verwenden:
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!
};