Risolvi le promesse uno dopo l’altro (cioè in sequenza)?

Si consideri il seguente codice che legge una matrice di file in modo seriale / sequenziale. readFiles restituisce una promise, che viene risolta solo dopo che tutti i file sono stati letti in sequenza.

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

Il codice sopra il codice funziona, ma non mi piace dover fare la ricorsione per le cose che si verificano in sequenza. C’è un modo più semplice per cui questo codice può essere riscritto in modo da non dover usare la mia strana funzione readSequential ?

Inizialmente ho provato a utilizzare Promise.all , ma questo ha causato il verificarsi simultaneo di tutte le chiamate readFile , il che non è quello che voglio:

 var readFiles = function(files) { return Promise.all(files.map(function(file) { return readFile(file); })); }; 

    Aggiornamento 2017 : userei una funzione asincrona se l’ambiente lo supporta:

     async function readFiles(files) { for(const file of files) { await readFile(file); } }; 

    Se lo desideri, puoi rimandare la lettura dei file fino a quando non ne hai bisogno usando un generatore asincrono (se il tuo ambiente lo supporta):

     async function* readFiles(files) { for(const file of files) { yield await readFile(file); } }; 

    Aggiornamento: In secondo luogo, potrei usare un ciclo for invece:

     var readFiles = function(files) { var p = Promise.resolve(); // Q() in q files.forEach(file => p = p.then(() => readFile(file)); ); return p; }; 

    O più compatto, con riduzione:

     var readFiles = function(files) { return files.reduce((p, file) => { return p.then(() => readFile); }, Promise.resolve()); // initial }; 

    In altre librerie di promesse (come quando e Bluebird) avete metodi di utilità per questo.

    Ad esempio, Bluebird sarebbe:

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

    Anche se non c’è davvero nessuna ragione per non usare async, oggi lo aspettiamo.

    Ecco come preferisco eseguire i compiti in 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 ----"); }); } 

    E i casi con più compiti? Tipo, 10?

     function runSerial(tasks) { var result = Promise.resolve(); tasks.forEach(task => { result = result.then(() => task()); }); return result; } 

    Questa domanda è vecchia, ma viviamo in un mondo di ES6 e JavaScript funzionale, quindi vediamo come possiamo migliorare.

    Poiché le promesse vengono eseguite immediatamente, non possiamo semplicemente creare una serie di promesse, ma tutte si attiverebbero in parallelo.

    Invece, abbiamo bisogno di creare una serie di funzioni che restituisca una promise. Ciascuna funzione verrà quindi eseguita in sequenza, quindi inizierà la promise all’interno.

    Possiamo risolvere questo in alcuni modi, ma il mio modo preferito è usare reduce .

    Diventa un po ‘complicato usare la reduce in combinazione con le promesse, quindi ho suddiviso l’una di linea in alcuni morsi più digeribili più piccoli qui sotto.

    L’essenza di questa funzione consiste nell’usare reduce iniziando con un valore iniziale di Promise.resolve([]) , o una promise contenente una matrice vuota.

    Questa promise verrà quindi passata al metodo di reduce come promise . Questa è la chiave per concatenare ogni promise insieme in sequenza. La prossima promise da eseguire è func e quando then triggers, i risultati vengono concatenati e tale promise viene quindi restituita, eseguendo il ciclo di reduce con la funzione successiva promise.

    Una volta che tutte le promesse sono state eseguite, la promise restituita conterrà una serie di tutti i risultati di ciascuna promise.

    Esempio ES6 (un rivestimento)

     /* * 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 Esempio (ripartito)

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

    Per fare questo semplicemente 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; } 

    Utile semplice per la promise Node.js standard:

     function sequence(tasks, fn) { return tasks.reduce((promise, task) => promise.then(() => fn(task)), Promise.resolve()); } 

    AGGIORNARE

    items-promise è un pacchetto NPM pronto all’uso che fa lo stesso.

    Ho dovuto eseguire un sacco di attività sequenziali e ho usato queste risposte per creare una funzione che si occupasse di gestire qualsiasi attività sequenziale …

     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 funzione accetta 2 argomenti + 1 opzionale. Il primo argomento è l’array su cui lavoreremo. Il secondo argomento è il compito stesso, una funzione che restituisce una promise, il prossimo compito verrà avviato solo quando questa promise si risolverà. Il terzo argomento è un callback da eseguire quando tutte le attività sono state eseguite. Se non viene passata alcuna richiamata, la funzione restituisce la promise che ha creato in modo che possiamo gestire la fine.

    Ecco un esempio di utilizzo:

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

    Spero che salvi qualcuno un po ‘di tempo …

    La soluzione migliore che sono riuscito a capire è stata la promise del bluebird . Puoi semplicemente fare Promise.resolve(files).each(fs.readFileAsync); che garantisce che le promesse siano risolte in ordine sequenziale.

    Questa è una leggera variazione di un’altra risposta sopra. Utilizzando le promesse native:

     function inSequence(tasks) { return tasks.reduce((p, task) => p.then(task), Promise.resolve()) } 

    Spiegazione

    Se hai questi compiti [t1, t2, t3] , allora quanto sopra è equivalente a Promise.resolve().then(t1).then(t2).then(t3) . È il comportamento di ridurre.

    Come usare

    Per prima cosa devi build una lista di compiti! Un’attività è una funzione che non accetta argomenti. Se è necessario passare argomenti alla propria funzione, utilizzare bind o altri metodi per creare un’attività. Per esempio:

     var tasks = files.map(file => processFile.bind(null, file)) inSequence(tasks).then(...) 

    La mia soluzione preferita:

     function processArray(arr, fn) { return arr.reduce( (p, v) => p.then((a) => fn(v).then(r => a.concat([r]))), Promise.resolve([]) ); } 

    Non è fondamentalmente diverso da altri pubblicati qui, ma:

    • Applica la funzione agli elementi in serie
    • Risolve in una serie di risultati
    • Non richiede async / await (il supporto è ancora abbastanza limitato, circa 2017)
    • Utilizza le funzioni freccia; bello e conciso

    Esempio di utilizzo:

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

    Testato su correnti correnti Chrome (v59) e NodeJS (v8.1.2).

    Ho creato questo semplice metodo sull’object Promise:

    Creare e aggiungere un metodo Promise.sequence all’object 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) {}); 

    La cosa migliore di questa estensione dell’object Promise è che è coerente con lo stile delle promesse. Promise.all e Promise.sequence vengono invocati allo stesso modo, ma hanno semantica diversa.

    Attenzione

    Il funzionamento sequenziale delle promesse di solito non è un ottimo modo per usare le promesse. Di solito è meglio usare Promise.all e lasciare che il browser esegua il codice il più velocemente ansible. Tuttavia, esistono casi di utilizzo reali, ad esempio durante la scrittura di un’app mobile con javascript.

    Puoi usare questa funzione che ti viene promise Elenco delle fabbriche:

     function executeSequentially(promiseFactories) { var result = Promise.resolve(); promiseFactories.forEach(function (promiseFactory) { result = result.then(promiseFactory); }); return result; } 

    Promise Factory è solo una semplice funzione che restituisce una promise:

     function myPromiseFactory() { return somethingThatCreatesAPromise(); } 

    Funziona perché una fabbrica di promesse non crea la promise finché non viene richiesta. Funziona allo stesso modo di una funzione allora – in effetti, è la stessa cosa!

    Non vuoi affatto operare su una serie di promesse. Per la specifica Promise, non appena viene creata una promise, inizia l’esecuzione. Quindi quello che vuoi veramente è una serie di fabbriche di promesse …

    Se vuoi saperne di più su Promises, dovresti controllare questo link: https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html

    Usa Array.prototype.reduce e ricorda di avvolgere le tue promesse in una funzione altrimenti sarebbero già in esecuzione!

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

    bello e facile … dovresti essere in grado di riutilizzare lo stesso seme per le prestazioni, ecc.

    Io uso il seguente codice per estendere l’object Promise. Gestisce il rifiuto delle promesse e restituisce una serie di risultati

    Codice

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

    Esempio

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

    Se vuoi puoi usare riduci per fare una promise sequenziale, ad esempio:

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

    Funzionerà sempre in sequenza.

    Mi è davvero piaciuta la risposta di @ joelnet, ma per me, quello stile di codifica è un po ‘difficile da digerire, quindi ho trascorso un paio di giorni cercando di capire come avrei express la stessa soluzione in un modo più leggibile e questo è il mio prendere, solo con una syntax diversa e alcuni commenti.

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

    Sulla base del titolo della domanda, “Risolvi le promesse uno dopo l’altro (cioè in sequenza)?”, Potremmo capire che il PO è più interessato alla gestione sequenziale delle promesse sull’insediamento delle chiamate sequenziali di per sé .

    Questa risposta è offerta:

    • per dimostrare che le chiamate sequenziali non sono necessarie per la gestione sequenziale delle risposte.
    • per esporre modelli alternativi fattibili ai visitatori di questa pagina – incluso l’OP se è ancora interessato più di un anno dopo.
    • nonostante l’affermazione dell’OP di non voler effettuare chiamate in modo concorrente, il che potrebbe essere vero, ma allo stesso modo può essere un’ipotesi basata sul desiderio di gestire sequenzialmente le risposte come suggerisce il titolo.

    Se le chiamate concorrenti non sono realmente ricercate, vedi la risposta di Benjamin Gruenbaum che copre le chiamate in sequenza (ecc.) In modo completo.

    Se, tuttavia, sei interessato (per prestazioni migliorate) a modelli che consentono chiamate simultanee seguite da una gestione sequenziale delle risposte, allora continua a leggere.

    È allettante pensare di dover usare Promise.all(arr.map(fn)).then(fn) (come ho fatto molte volte) o uno zucchero di fantasia di Promise lib (in particolare Bluebird’s), comunque (con merito a questo articolo ) un arr.map(fn).reduce(fn) farà il lavoro, con i vantaggi che esso:

    • funziona con qualsiasi lib di promise – anche con versioni pre-conformi di jQuery – viene utilizzato solo .then() .
    • offre la flessibilità di saltare sopra l’errore o stop-on-error, a seconda di ciò che si desidera con una mod di una riga.

    Eccolo, scritto per 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 quel frammento, Q() , è specifico di Q. Per jQuery è necessario assicurarsi che readFile () restituisca una promise jQuery. Con A + libs, le promesse straniere saranno assimilate.

    La chiave qui è la promise della sequence di riduzione, che sequenze la gestione delle promesse readFile ma non la loro creazione.

    E una volta che l’hai assorbito, è forse un po ‘sbalorditivo quando ti rendi conto che la fase .map() non è effettivamente necessaria! L’intero lavoro, le chiamate parallele e la gestione seriale nell’ordine corretto, possono essere raggiunti con reduce() da solo, oltre al vantaggio aggiuntivo di un’ulteriore flessibilità per:

    • convertire da chiamate asincrone parallele a chiamate asincrone seriate semplicemente spostando una linea – potenzialmente utile durante lo sviluppo.

    Eccolo, per Q nuovo.

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

    Questo è lo schema di base. Se si desidera fornire anche dati (ad esempio i file o qualche trasformazione di essi) al chiamante, è necessaria una variante lieve.

    Si tratta di estendere su come elaborare una sequenza di promesse in un modo più generico, supportando sequenze dinamiche / infinite, basate sull’implementazione di 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; }); 

    Non solo questa soluzione funzionerà con sequenze di qualsiasi dimensione, ma è ansible aggiungere facilmente la limitazione dei dati e il bilanciamento del carico .

    Il tuo approccio non è male, ma ha due problemi: ingoia gli errori e utilizza l’Explicit Promise Construction Antipattern.

    Puoi risolvere entrambi questi problemi e rendere il codice più pulito, pur utilizzando la stessa strategia generale:

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

    La mia risposta è basata su 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; }); }; 

    Questa soluzione restituisce i risultati come una matrice come Promise.all ().

    Uso:

     Promise.series([array of promises]) .then(function(results) { // do stuff with results here }); 

    Se qualcun altro ha bisogno di un modo garantito di STRISCIAMENTE modalità sequenziale per la risoluzione delle Promesse durante l’esecuzione delle operazioni CRUD, è anche ansible utilizzare il seguente codice come base.

    Finché si aggiunge “return” prima di chiamare ogni funzione, descrivendo una Promessa e si utilizza questo esempio come base, la successiva chiamata alla funzione .then () inizierà in modo COSTANTE dopo il completamento della precedente:

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