¿Node.js es nativo de Promise.all procesándose en paralelo o secuencialmente?

173

Me gustaría aclarar este punto, ya que la documentación no es muy clara al respecto;

P1: ¿Se Promise.all(iterable)procesan todas las promesas de forma secuencial o en paralelo? O, más específicamente, ¿es el equivalente a ejecutar promesas encadenadas como

p1.then(p2).then(p3).then(p4).then(p5)....

o se trata de otro tipo de algoritmo donde todos p1, p2, p3, p4, p5, etc., están siendo llamados al mismo tiempo (en paralelo) y los resultados son devueltos tan pronto como todos determinación (o se rechaza)?

P2: Si se Promise.allejecuta en paralelo, ¿hay una manera conveniente de ejecutar una secuencia iterable?

Nota : No quiero usar Q, o Bluebird, sino todas las especificaciones nativas de ES6.

Yanick Rochon
fuente
¿Está preguntando sobre la implementación del nodo (V8), o sobre la especificación?
Amit
1
Estoy bastante seguro de que los Promise.allejecuta en paralelo.
royhowie
@Amití que marqué node.jsy io.jsya que aquí es donde lo estoy usando. Entonces, sí, la implementación de V8 si se quiere.
Yanick Rochon
9
Las promesas no pueden "ser ejecutadas". Comienzan su tarea cuando se están creando , solo representan los resultados, y usted está ejecutando todo en paralelo incluso antes de pasarlos Promise.all.
Bergi
Las promesas se ejecutan en el momento de la creación. (puede confirmarse ejecutando un poco de código). En new Promise(a).then(b); c();a se ejecuta primero, luego c, luego b. No es Promise.todo lo que ejecuta estas promesas, solo se maneja cuando se resuelven.
Mateon1

Respuestas:

257

¿Está Promise.all(iterable)ejecutando todas las promesas?

No, las promesas no pueden "ejecutarse". Comienzan su tarea cuando se están creando , solo representan los resultados, y usted está ejecutando todo en paralelo incluso antes de pasarlos Promise.all.

Promise.allsolo espera múltiples promesas. No le importa en qué orden resuelven, o si los cálculos se ejecutan en paralelo.

¿Hay alguna manera conveniente de ejecutar una secuencia iterable?

Si ya tienes tus promesas, no puedes hacer mucho pero Promise.all([p1, p2, p3, …])(que no tiene una noción de secuencia). Pero si tiene un iterable de funciones asincrónicas, puede ejecutarlas secuencialmente. Básicamente necesitas llegar de

[fn1, fn2, fn3, …]

a

fn1().then(fn2).then(fn3).then(…)

y la solución para hacer eso es usar Array::reduce:

iterable.reduce((p, fn) => p.then(fn), Promise.resolve())
Bergi
fuente
1
En este ejemplo, ¿es iterable un conjunto de funciones que devuelven una promesa a la que desea llamar?
James Reategui
2
@SSHThis: es exactamente como la thensecuencia: el valor de retorno es la promesa para el último fnresultado, y puede encadenar otras devoluciones de llamada a eso.
Bergi
1
@wojjas Eso es exactamente equivalente a fn1().then(p2).then(fn3).catch(…? No es necesario usar una expresión de función.
Bergi
1
@wojjas Por supuesto que retValFromF1se pasa p2, eso es exactamente lo que p2hace. Claro, si desea hacer más (pasar variables adicionales, llamar a múltiples funciones, etc.) debe usar una expresión de función, aunque cambiar p2en la matriz sería más fácil
Bergi
1
@ robe007 Sí, quise decir que esa iterablees la [fn1, fn2, fn3, …]matriz
Bergi
62

En paralelo

await Promise.all(items.map(async item => { await fetchItem(item) }))

Ventajas: más rápido. Todas las iteraciones se ejecutarán incluso si una falla.

En secuencia

for (let i = 0; i < items.length; i++) {
    await fetchItem(items[i])
}

Ventajas: las variables en el bucle pueden ser compartidas por cada iteración. Se comporta como un código síncrono imperativo normal.

david_adler
fuente
77
O:for (const item of items) await fetchItem(item);
Robert Penner
1
@david_adler En ventajas de ejemplo paralelas, usted dijo Todas las iteraciones se ejecutarán incluso si una falla . Si no me equivoco, esto todavía fallaría rápidamente. Para cambiar este comportamiento uno puede hacer algo como: await Promise.all(items.map(async item => { return await fetchItem(item).catch(e => e) }))
Taimoor
@Taimoor sí, "falla rápidamente" y continúa ejecutando código después de Promise.all pero todas las iteraciones aún se ejecutan codepen.io/mfbx9da4/pen/BbaaXr
david_adler
Este enfoque es mejor, cuando la asyncfunción es una llamada API y no desea DDOS al servidor. Tiene un mejor control sobre los resultados individuales y los errores arrojados en la ejecución. Aún mejor, puede decidir qué errores continuar y qué romper el ciclo.
mandarina
Tenga en cuenta que javascript no está ejecutando realmente las solicitudes asincrónicas en "paralelo" usando subprocesos ya que javascript es de un solo subproceso. developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
david_adler
11

La respuesta de Bergis me puso en el camino correcto usando Array.reduce.

Sin embargo, para que las funciones devuelvan mis promesas de ejecutarse una tras otra, tuve que agregar un poco más de anidamiento.

Mi caso de uso real es una matriz de archivos que necesito transferir en orden uno tras otro debido a los límites posteriores ...

Aquí es con lo que terminé.

getAllFiles().then( (files) => {
    return files.reduce((p, theFile) => {
        return p.then(() => {
            return transferFile(theFile); //function returns a promise
        });
    }, Promise.resolve()).then(()=>{
        console.log("All files transferred");
    });
}).catch((error)=>{
    console.log(error);
});

Como sugieren las respuestas anteriores, usando:

getAllFiles().then( (files) => {
    return files.reduce((p, theFile) => {
        return p.then(transferFile(theFile));
    }, Promise.resolve()).then(()=>{
        console.log("All files transferred");
    });
}).catch((error)=>{
    console.log(error);
});

no esperó a que se completara la transferencia antes de comenzar otra y también el texto "Todos los archivos transferidos" llegó incluso antes de que se iniciara la primera transferencia de archivos.

No estoy seguro de lo que hice mal, pero quería compartir lo que funcionó para mí.

Editar: desde que escribí esta publicación, ahora entiendo por qué la primera versión no funcionó. then () espera que una función devuelva una promesa. Por lo tanto, debe pasar el nombre de la función sin paréntesis. Ahora, mi función quiere un argumento, ¡entonces necesito envolverme en una función anónima sin tomar ningún argumento!

tkarls
fuente
4

solo para elaborar sobre la respuesta de @ Bergi (que es muy sucinta, pero difícil de entender;)

Este código ejecutará cada elemento de la matriz y agregará la siguiente 'cadena' al final;

function eachorder(prev,order) {
        return prev.then(function() {
          return get_order(order)
            .then(check_order)
            .then(update_order);
        });
    }
orderArray.reduce(eachorder,Promise.resolve());

Espero que tenga sentido.

TimoSolo
fuente
3

También puede procesar un iterable secuencialmente con una función asíncrona utilizando una función recursiva. Por ejemplo, dada una matriz apara procesar con función asincrónica someAsyncFunction():

var a = [1, 2, 3, 4, 5, 6]

function someAsyncFunction(n) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("someAsyncFunction: ", n)
      resolve(n)
    }, Math.random() * 1500)
  })
}

//You can run each array sequentially with: 

function sequential(arr, index = 0) {
  if (index >= arr.length) return Promise.resolve()
  return someAsyncFunction(arr[index])
    .then(r => {
      console.log("got value: ", r)
      return sequential(arr, index + 1)
    })
}

sequential(a).then(() => console.log("done"))

Mark Meyer
fuente
usar array.prototype.reducees mucho mejor en términos de rendimiento que una función recursiva
Mateusz Sowiński
@ MateuszSowiński, hay un tiempo de espera de 1500 ms entre cada llamada. Teniendo en cuenta que esto está haciendo llamadas asíncronas secuencialmente, es difícil ver cómo eso es relevante incluso para un cambio asincrónico muy rápido.
Mark Meyer
Supongamos que tiene que ejecutar 40 funciones asíncronas realmente rápidas una tras otra - el uso de funciones recursivas obstruiría su memoria bastante rápido
Mateusz Sowiński
@ MateuszSowiński, que la pila no termina aquí ... estamos regresando después de cada llamada. Compare eso con reducedonde tiene que construir toda la then()cadena en un solo paso y luego ejecutar.
Mark Meyer
En la 40.a llamada de la función secuencial, la primera llamada de la función todavía está en la memoria esperando que regrese la cadena de funciones secuenciales
Mateusz Sowiński
3

NodeJS no ejecuta promesas en paralelo, las ejecuta simultáneamente, ya que se trata de una arquitectura de bucle de eventos de subproceso único. Existe la posibilidad de ejecutar cosas en paralelo creando un nuevo proceso hijo para aprovechar la CPU de múltiples núcleos.

Paralelo contra Concurrente

De hecho, lo que Promise.allhace es apilar la función de promesas en la cola apropiada (ver arquitectura de bucle de eventos) ejecutándolas simultáneamente (llamar a P1, P2, ...) y luego esperar cada resultado, luego resolver Promise.all con todas las promesas resultados. Promise.all fallará en la primera promesa que falle, a menos que usted mismo haya logrado el rechazo.

Existe una gran diferencia entre paralelo y concurrente, el primero ejecutará un cálculo diferente en un proceso separado exactamente al mismo tiempo y progresará en ese mismo ritmo, mientras que el otro ejecutará el cálculo diferente uno tras otro sin esperar al anterior cálculo para terminar y progresar al mismo tiempo sin depender el uno del otro.

Finalmente, para responder a su pregunta, Promise.allno se ejecutará ni en paralelo ni secuencialmente, sino al mismo tiempo.

Adrien De Peretti
fuente
Esto no está bien. NodeJS puede ejecutar cosas en paralelo. NodeJS tiene un concepto de hilo de trabajo. De manera predeterminada, el número de subprocesos de trabajo es 4. Por ejemplo, si usa la biblioteca criptográfica para trocear dos valores, puede ejecutarlos en paralelo. Dos hilos de trabajo manejarán la tarea. Por supuesto, su CPU debe ser multinúcleo para admitir paralelismo.
Shihab
Sí, claro, es lo que dije al final del primer párrafo, pero hablé sobre el proceso secundario, por supuesto, pueden dirigir trabajadores.
Adrien De Peretti
2

El uso de async espera una serie de promesas que se pueden ejecutar fácilmente de forma secuencial:

let a = [promise1, promise2, promise3];

async function func() {
  for(let i=0; i<a.length; i++){
    await a[i]();
  }  
}

func();

Nota: en la implementación anterior, si se rechaza una promesa, el resto no se ejecutará. Si desea que se ejecuten todas sus promesas, envuelva su await a[i]();interiortry catch

Ayan
fuente
2

paralela

mira este ejemplo

const resolveAfterTimeout = async i => {
  return new Promise(resolve => {
    console.log("CALLED");
    setTimeout(() => {
      resolve("RESOLVED", i);
    }, 5000);
  });
};

const call = async () => {
  const res = await Promise.all([
    resolveAfterTimeout(1),
    resolveAfterTimeout(2),
    resolveAfterTimeout(3),
    resolveAfterTimeout(4),
    resolveAfterTimeout(5),
    resolveAfterTimeout(6)
  ]);
  console.log({ res });
};

call();

al ejecutar el código, consolará "CALLED" para las seis promesas y cuando se resuelvan, consolará cada 6 respuestas después del tiempo de espera al mismo tiempo

Chintan Rajpara
fuente
1

La respuesta de Bergi me ayudó a hacer que la llamada fuera síncrona. He agregado un ejemplo a continuación donde llamamos a cada función después de que se llama a la función anterior.

function func1 (param1) {
    console.log("function1 : " + param1);
}
function func2 () {
    console.log("function2");
}
function func3 (param2, param3) {
    console.log("function3 : " + param2 + ", " + param3);
}

function func4 (param4) {
    console.log("function4 : " + param4);
}
param4 = "Kate";

//adding 3 functions to array

a=[
    ()=>func1("Hi"),
    ()=>func2(),
    ()=>func3("Lindsay",param4)
  ];

//adding 4th function

a.push(()=>func4("dad"));

//below does func1().then(func2).then(func3).then(func4)

a.reduce((p, fn) => p.then(fn), Promise.resolve());
Nithi
fuente
¿Es esta una respuesta a la pregunta original?
Giulio Caccin
0

Puedes hacerlo por bucle.

promesa de devolución de función asíncrona

async function createClient(client) {
    return await Client.create(client);
}

let clients = [client1, client2, client3];

si escribe el siguiente código, el cliente se crea paralelamente

const createdClientsArray = yield Promise.all(clients.map((client) =>
    createClient(client);
));

entonces todos los clientes se crean paralelamente. pero si desea crear un cliente secuencialmente, debe usar for loop

const createdClientsArray = [];
for(let i = 0; i < clients.length; i++) {
    const createdClient = yield createClient(clients[i]);
    createdClientsArray.push(createdClient);
}

entonces todos los clientes se crean secuencialmente.

feliz codificación :)

Deepak Sisodiya
fuente
8
En este momento, async/ awaitsolo está disponible con un transpilador o con otros motores que nodos. Además, realmente no se debe mezclar asynccon yield. Cuando actúan de la misma manera con un transpilador co, realmente son bastante diferentes y normalmente no deberían sustituirse entre sí. Además, debe mencionar estas restricciones ya que su respuesta es confusa para los programadores novatos.
Yanick Rochon
0

He estado usando para para resolver promesas secuenciales. No estoy seguro de si ayuda aquí, pero esto es lo que he estado haciendo.

async function run() {
    for (let val of arr) {
        const res = await someQuery(val)
        console.log(val)
    }
}

run().then().catch()
Nick Kotenberg
fuente
0

Esto podría responder parte de su pregunta.

sí, puede encadenar una serie de funciones de devolución de promesa de la siguiente manera ... (esto pasa el resultado de cada función a la siguiente). por supuesto, podría editarlo para pasar el mismo argumento (o ningún argumento) a cada función.

function tester1(a) {
  return new Promise(function(done) {
    setTimeout(function() {
      done(a + 1);
    }, 1000);
  })
}

function tester2(a) {
  return new Promise(function(done) {
    setTimeout(function() {
      done(a * 5);
    }, 1000);
  })
}

function promise_chain(args, list, results) {

  return new Promise(function(done, errs) {
    var fn = list.shift();
    if (results === undefined) results = [];
    if (typeof fn === 'function') {
      fn(args).then(function(result) {
        results.push(result);
        console.log(result);
        promise_chain(result, list, results).then(done);
      }, errs);
    } else {
      done(results);
    }

  });

}

promise_chain(0, [tester1, tester2, tester1, tester2, tester2]).then(console.log.bind(console), console.error.bind(console));

Soy yo
fuente
0

Me topé con esta página mientras intentaba resolver un problema en NodeJS: reensamblaje de fragmentos de archivos. Básicamente: tengo una variedad de nombres de archivo. Necesito agregar todos esos archivos, en el orden correcto, para crear un archivo grande. Debo hacer esto asincrónicamente.

El módulo 'fs' de Node proporciona appendFileSync pero no quise bloquear el servidor durante esta operación. Quería usar el módulo fs.promises y encontrar una manera de encadenar todo esto. Los ejemplos en esta página no funcionaron para mí porque realmente necesitaba dos operaciones: fsPromises.read () para leer en el fragmento de archivo y fsPromises.appendFile () para concatenar con el archivo de destino. Tal vez si fuera mejor con javascript podría haber hecho que las respuestas anteriores funcionen para mí. ;-)

Me topé con esto ... https://css-tricks.com/why-using-reduce-to-sequentially-resolve-promises-works/ ... y pude hackear juntos una solución de trabajo.

TLDR:

/**
 * sequentially append a list of files into a specified destination file
 */
exports.append_files = function (destinationFile, arrayOfFilenames) {
    return arrayOfFilenames.reduce((previousPromise, currentFile) => {
        return previousPromise.then(() => {
            return fsPromises.readFile(currentFile).then(fileContents => {
                return fsPromises.appendFile(destinationFile, fileContents);
            });
        });
    }, Promise.resolve());
};

Y aquí hay una prueba de unidad de jazmín para ello:

const fsPromises = require('fs').promises;
const fsUtils = require( ... );
const TEMPDIR = 'temp';

describe("test append_files", function() {
    it('append_files should work', async function(done) {
        try {
            // setup: create some files
            await fsPromises.mkdir(TEMPDIR);
            await fsPromises.writeFile(path.join(TEMPDIR, '1'), 'one');
            await fsPromises.writeFile(path.join(TEMPDIR, '2'), 'two');
            await fsPromises.writeFile(path.join(TEMPDIR, '3'), 'three');
            await fsPromises.writeFile(path.join(TEMPDIR, '4'), 'four');
            await fsPromises.writeFile(path.join(TEMPDIR, '5'), 'five');

            const filenameArray = [];
            for (var i=1; i < 6; i++) {
                filenameArray.push(path.join(TEMPDIR, i.toString()));
            }

            const DESTFILE = path.join(TEMPDIR, 'final');
            await fsUtils.append_files(DESTFILE, filenameArray);

            // confirm "final" file exists    
            const fsStat = await fsPromises.stat(DESTFILE);
            expect(fsStat.isFile()).toBeTruthy();

            // confirm content of the "final" file
            const expectedContent = new Buffer('onetwothreefourfive', 'utf8');
            var fileContents = await fsPromises.readFile(DESTFILE);
            expect(fileContents).toEqual(expectedContent);

            done();
        }
        catch (err) {
            fail(err);
        }
        finally {
        }
    });
});

Espero que esto ayude a alguien.

Arrendajo
fuente