¿Cómo puedo ejecutar una serie de promesas en orden secuencial?

81

Tengo una serie de promesas que deben ejecutarse en orden secuencial.

var promises = [promise1, promise2, ..., promiseN];

Llamar a RSVP.all los ejecutará en paralelo:

RSVP.all(promises).then(...); 

Pero, ¿cómo puedo ejecutarlos en secuencia?

Puedo apilarlos manualmente así

RSVP.resolve()
    .then(promise1)
    .then(promise2)
    ...
    .then(promiseN)
    .then(...);

pero el problema es que el número de promesas varía y el conjunto de promesas se construye dinámicamente.

jaaksarv
fuente
de las otras respuestas y votos negativos en la mía, parece que más gente necesita leer el README de rsvp donde explica "La parte realmente asombrosa viene cuando devuelves una promesa del primer controlador". Si no está haciendo esto, realmente se está perdiendo el poder expresivo de las promesas.
Michael Johnston
Pregunta similar pero no específica del marco: stackoverflow.com/q/24586110/245966
jakub.g

Respuestas:

136

Si ya los tiene en una matriz, entonces ya se están ejecutando. Si tiene una promesa, entonces ya se está ejecutando. Esto no es una preocupación de las promesas (es decir, no son como C # Tasks en ese sentido con el .Start()método). .allno ejecuta nada, solo devuelve una promesa.

Si tiene una serie de funciones de devolución de promesa:

var tasks = [fn1, fn2, fn3...];

tasks.reduce(function(cur, next) {
    return cur.then(next);
}, RSVP.resolve()).then(function() {
    //all executed
});

O valores:

var idsToDelete = [1,2,3];

idsToDelete.reduce(function(cur, next) {
    return cur.then(function() {
        return http.post("/delete.php?id=" + next);
    });
}, RSVP.resolve()).then(function() {
    //all executed
});
Esailija
fuente
3
esta es una excelente manera de construir un árbol de promesas homogéneas que no requieren argumentos. Es exactamente equivalente a usar un puntero next_promise para construir el árbol usted mismo, lo que debe hacer si el conjunto de promesas no es homogéneo con respecto a los argumentos, etc. Es solo que la función de reducción está haciendo el puntero a la corriente -hoja un poco para ti. También querrá construir el árbol de usted mismo si algunas de sus cosas pueden suceder al mismo tiempo. En un árbol de promesas, las ramas son secuencias y las hojas son concurrentes.
Michael Johnston
Gracias por su respuesta. Tiene razón en que crear una promesa ya significa que se está ejecutando, por lo que mi pregunta no se formó correctamente. Terminé resolviendo mi problema de manera diferente sin promesas.
jaaksarv
1
@SSHThis Bueno primero que nada, wat. En segundo lugar, se pasa la respuesta anterior a .then, en este ejemplo simplemente se ignora ...
Esailija
3
Si alguna de estas promesas falla, el error nunca será rechazado y la promesa nunca se resolverá ...
Maxwelll
5
Si ya los tiene en una matriz, entonces ya se están ejecutando. - esta frase debe estar en negrita + fuente más grande. Es fundamental comprender.
ducin
22

Con las funciones asíncronas de ECMAScript 2017 se haría así:

async function executeSequentially() {
    const tasks = [fn1, fn2, fn3]

    for (const fn of tasks) {
        await fn()
    }
}

Puede usar BabelJS para usar funciones asíncronas ahora

ujeenator
fuente
Este debería ser el enfoque predeterminado a estas alturas (2020). Para los usuarios primerizos, puede ser importante tener en cuenta dos cosas aquí: 1. Una vez que existe una promesa, ya está vigente. Así que es realmente importante que 2. fn1, fn2, fn3aquí estén las funciones, por ejemplo () => yourFunctionReturningAPromise(), en lugar de solo yourFunctionReturningAPromise(). Esta es también la razón por la que await fn()es necesario en lugar de simplemente await fn. Vea más en los documentos oficiales . Perdón por publicar como comentario, pero la cola de edición está llena :)
ezmegy
7

ES7 camino en 2017.

  <script>
  var funcs = [
    _ => new Promise(resolve => setTimeout(_ => resolve("1"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("2"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("3"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("4"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("5"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("6"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("7"), 1000))
  ];
  async function runPromisesInSequence(promises) {
    for (let promise of promises) {
      console.log(await promise());
    }
  }
  </script>
  <button onClick="runPromisesInSequence(funcs)">Do the thing</button>

Esto ejecutará las funciones dadas secuencialmente (una por una), no en paralelo. El parámetro promiseses una matriz de funciones, que regresan Promise.

Ejemplo de Plunker con el código anterior: http://plnkr.co/edit/UP0rhD?p=preview

allenhwkim
fuente
4

Un segundo intento de respuesta en el que trato de ser más explicativo:

Primero, algunos antecedentes necesarios, del README RSVP :

La parte realmente asombrosa llega cuando devuelve una promesa del primer controlador ... Esto le permite aplanar las devoluciones de llamada anidadas, y es la característica principal de las promesas que evita la "deriva hacia la derecha" en programas con mucho código asincrónico.

Así es precisamente como haces promesas secuenciales, devolviendo la promesa posterior del thende la promesa que debería terminar antes.

Es útil pensar en un conjunto de promesas como un árbol, donde las ramas representan procesos secuenciales y las hojas representan procesos concurrentes.

El proceso de construir un árbol de promesas de este tipo es análogo a la tarea muy común de construir otros tipos de árboles: mantenga un puntero o referencia al lugar del árbol en el que está agregando ramas actualmente y agregue cosas de forma iterativa.

Como @Esailija señaló en su respuesta, si tiene una variedad de funciones de devolución de promesas que no aceptan argumentos, puede usar reducepara construir ordenadamente el árbol para usted. Si alguna vez ha implementado reduce por sí mismo, comprenderá que lo que reduce detrás de escena en la respuesta de @ Esailija es mantener una referencia a la promesa actual ( cur) y hacer que cada promesa devuelva la siguiente promesa en su then.

Si NO tiene una buena matriz de funciones de devolución de promesas homogéneas (con respecto a los argumentos que toman / devuelven), o si necesita una estructura más complicada que una secuencia lineal simple, puede construir el árbol de promesas usted mismo manteniendo una referencia a la posición en el árbol de promesas donde desea agregar nuevas promesas:

var root_promise = current_promise = Ember.Deferred.create(); 
// you can also just use your first real promise as the root; the advantage of  
// using an empty one is in the case where the process of BUILDING your tree of 
// promises is also asynchronous and you need to make sure it is built first 
// before starting it

current_promise = current_promise.then(function(){
  return // ...something that returns a promise...;
});

current_promise = current_promise.then(function(){
  return // ...something that returns a promise...;
});

// etc.

root_promise.resolve();

Puede crear combinaciones de procesos simultáneos y secuenciales utilizando RSVP.all para agregar múltiples "hojas" a una "rama" de promesa. Mi respuesta votada negativamente por ser demasiado complicada muestra un ejemplo de eso.

También puede usar Ember.run.scheduleOnce ('afterRender') para asegurarse de que algo hecho en una promesa se procese antes de que se active la siguiente; mi respuesta votada negativamente por ser demasiado complicada también muestra un ejemplo de eso.

Michael Johnston
fuente
3
Esto es mucho mejor, sin embargo, siento que todavía te estás desviando del tema. Esto es común a muchas respuestas sobre promesas, las personas no parecen tomarse el tiempo para leer la pregunta, sino que simplemente comentan sobre algún aspecto de las promesas que comprenden personalmente. La pregunta original no implica una ejecución paralela, ni siquiera un poco, y muestra claramente que simplemente thense desea encadenar a través , ha proporcionado mucha información adicional que oculta la respuesta a la pregunta que se hizo.
David McMullin
@DavidMcMullin ".... y muestra claramente que simplemente se desea encadenar a través de entonces ..." pero en realidad afirma que la secuencia de promesas se construye dinámicamente. Así que necesita entender cómo construir un árbol, incluso si en este caso es el subconjunto simple de la "secuencia lineal" del árbol. Aún tiene que construirlo manteniendo una referencia a la última promesa de la cadena y agregando nuevas promesas.
Michael Johnston
Cuando OP dijo que "la cantidad de promesas varía y la matriz de promesas se construye dinámicamente", estoy bastante seguro de que todo lo que quería decir era que el tamaño de la matriz no estaba predeterminado y que, por lo tanto, no podía usar un simple Promise.resolve().then(...).then(...)..., no es que la matriz estuviera creciendo mientras se ejecutaban las promesas. Por supuesto, ahora todo es discutible.
JLRishe
4

Otro enfoque más es definir una función de secuencia global en el Promiseprototipo.

Promise.prototype.sequence = async (promiseFns) => {
  for (let promiseFn of promiseFns) {
    await promiseFn();
  }
}

Entonces puedes usarlo en cualquier lugar, como Promise.all()

Ejemplo

const timeout = async ms => new Promise(resolve =>
  setTimeout(() => {
    console.log("done", ms);
    resolve();
  }, ms)
);

// Executed one after the other
await Promise.sequence([() => timeout(1000), () => timeout(500)]);
// done: 1000
// done: 500

// Executed in parallel
await Promise.all([timeout(1000), timeout(500)]);
// done: 500
// done: 1000

Descargo de responsabilidad: ¡tenga cuidado al editar prototipos!

Ben Winding
fuente
2

Todo es necesario para resolver eso es un forbucle :)

var promises = [a,b,c];
var chain;

for(let i in promises){
  if(chain) chain = chain.then(promises[i]);
  if(!chain) chain = promises[i]();
}

function a(){
  return new Promise((resolve)=>{
    setTimeout(function(){
      console.log('resolve A');
      resolve();
    },1000);
  });
}
function b(){
  return new Promise((resolve)=>{
    setTimeout(function(){
      console.log('resolve B');
      resolve();
    },500);
  });
}
function c(){
  return new Promise((resolve)=>{
    setTimeout(function(){
      console.log('resolve C');
      resolve();
    },100);
  });
}
Paweł
fuente
¿Por qué if(!chain) chain = promises[i]();tiene un ()al final? Creo que en el caso de que la cadena esté vacía (iteración 0), uno solo querría tener la promesa en bruto, y luego el ciclo puede inyectar cada promesa posterior en la de la cadena .then(). Entonces, ¿no sería esto if(!chain) chain = promises[i];? Quizás no he entendido algo aquí.
Halfer
Ah, de a,b,checho son funciones que devuelven promesas, y no promesas. Entonces lo anterior tiene sentido. Pero, ¿de qué sirve envolver las Promesas de esta manera?
Halfer
2

Tuve un problema similar e hice una función recursiva que ejecuta funciones una por una secuencialmente.

var tasks = [fn1, fn2, fn3];

var executeSequentially = function(tasks) {
  if (tasks && tasks.length > 0) {
    var task = tasks.shift();

    return task().then(function() {
      return executeSequentially(tasks);
    });
  }

  return Promise.resolve();  
};

En caso de que necesite recopilar resultados de estas funciones:

var tasks = [fn1, fn2, fn3];

var executeSequentially = function(tasks) {
  if (tasks && tasks.length > 0) {
    var task = tasks.shift();

    return task().then(function(output) {
      return executeSequentially(tasks).then(function(outputs) {
        outputs.push(output);

        return Promise.resolve(outputs);  
      });
    });
  }

  return Promise.resolve([]);
};
mrded
fuente
0
export type PromiseFn = () => Promise<any>;

export class PromiseSequence {
  private fns: PromiseFn[] = [];

  push(fn: PromiseFn) {
    this.fns.push(fn)
  }

  async run() {
    for (const fn of this.fns) {
      await fn();
    }
  }
}

entonces

const seq = new PromiseSequence();
seq.push(() => Promise.resolve(1));
seq.push(() => Promise.resolve(2));
seq.run();

también es posible almacenar lo que las promesas regresan en otra var privada y pasarlo a devoluciones de llamada

Daniel Khoroshko
fuente
-1

Lo que buscaba era esencialmente mapSeries, y resulta que estoy mapeando guardar sobre un conjunto de valores, y quiero los resultados.

Entonces, aquí está todo lo que llegué, para ayudar a otros a buscar cosas similares en el futuro ...

(Tenga en cuenta que el contexto es una aplicación Ember).

App = Ember.Application.create();

App.Router.map(function () {
    // put your routes here
});

App.IndexRoute = Ember.Route.extend({
    model: function () {
            var block1 = Em.Object.create({save: function() {
                return Em.RSVP.resolve("hello");
            }});
    var block2 = Em.Object.create({save: function() {
            return Em.RSVP.resolve("this");
        }});
    var block3 = Em.Object.create({save: function() {
        return Em.RSVP.resolve("is in sequence");
    }});

    var values = [block1, block2, block3];

    // want to sequentially iterate over each, use reduce, build an array of results similarly to map...

    var x = values.reduce(function(memo, current) {
        var last;
        if(memo.length < 1) {
            last = current.save();
        } else {
            last = memo[memo.length - 1];
        }
        return memo.concat(last.then(function(results) {
            return current.save();
        }));
    }, []);

    return Ember.RSVP.all(x);
    }
});
Julian Leviston
fuente