Búsqueda de sitios web

javascript - Llamar funciones async/await en paralelo


Hasta donde entiendo, en ES7/ES2016 poner múltiples await en el código funcionará de manera similar a encadenar .then() con promesas, lo que significa que se ejecutarán una tras otra en lugar de en paralelo. Por ejemplo, tenemos este código:

await someCall();
await anotherCall();

¿Entiendo correctamente que anotherCall() se llamará solo cuando someCall() haya terminado? ¿Cuál es la forma más elegante de llamarlas en paralelo?

Quiero usarlo en Node, así que tal vez haya una solución con la biblioteca async?

EDIT: No estoy satisfecho con la solución proporcionada en esta pregunta: Lentitud debido a la espera no paralela de promesas en generadores asíncronos, porque utiliza generadores y estoy preguntando sobre un caso de uso más general.

Respuesta #1

Puedes esperar en Promise.all():

await Promise.all([someCall(), anotherCall()]);

Para almacenar los resultados:

let [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);

Tenga en cuenta que Promise.all falla rápido, lo que significa que tan pronto como una de las promesas que se le suministran se rechaza, entonces todo se rechaza.

const happy = (v, ms) => new Promise((resolve) => setTimeout(() => resolve(v), ms))
const sad = (v, ms) => new Promise((_, reject) => setTimeout(() => reject(v), ms))

Promise.all([happy('happy', 100), sad('sad', 50)])
  .then(console.log).catch(console.log) // 'sad'

Si, en cambio, quieres esperar a que todas las promesas se cumplan o se rechacen, entonces puedes usar Promise.allSettled. Ten en cuenta que Internet Explorer no admite este método de forma nativa.

const happy = (v, ms) => new Promise((resolve) => setTimeout(() => resolve(v), ms))
const sad = (v, ms) => new Promise((_, reject) => setTimeout(() => reject(v), ms))

Promise.allSettled([happy('happy', 100), sad('sad', 50)])
  .then(console.log) // [{ "status":"fulfilled", "value":"happy" }, { "status":"rejected", "reason":"sad" }]

Nota: Si usa Promise.all, las acciones que lograron completarse antes de que ocurriera un rechazo no se revierten, por lo que puede necesitar encargarse de tal situación. Por ejemplo, si tiene 5 acciones, 4 rápidas y 1 lenta y la lenta falla. Esas 4 acciones pueden haberse ejecutado ya, por lo que podría necesitar revertirlas. En tal situación, considere usar Promise.allSettled, ya que le proporcionará detalles exactos sobre qué acción falló y cuál no.

Respuesta #2

Resumen

Usa Promise.all para las llamadas de función en paralelo, el comportamiento de la respuesta no es correcto cuando ocurre un error.

Primero, ejecuta todas las llamadas asincrónicas a la vez y obtén todos los objetos Promise. Segundo, usa await en los objetos Promise. De esta manera, mientras esperas a que se resuelva la primera Promise, las demás llamadas asincrónicas siguen avanzando. En general, solo esperarás tanto como la llamada asincrónica más lenta. Por ejemplo:

// Begin first call and store promise without waiting
const someResult = someCall();

// Begin second call and store promise without waiting
const anotherResult = anotherCall();

// Now we await for both results, whose async processes have already been started
const finalResult = [await someResult, await anotherResult];

// At this point all calls have been resolved
// Now when accessing someResult| anotherResult,
// you will have a value instead of a promise

Ejemplo de JSbin: http://jsbin.com/xerifanima/edit?js,console

Aviso: No importa si las llamadas await están en la misma línea o en líneas diferentes, mientras que la primera llamada await ocurra después de todas las llamadas asíncronas. Vea el comentario de JohnnyHK.

Actualización: esta respuesta tiene un momento diferente en el manejo de errores según la respuesta de @bergi, NO lanza el error cuando ocurre, sino después de que todas las promesas se han ejecutado. Comparo el resultado con el consejo de @jonny: [result1, result2]=Promise.all([async1(), async2()]), revisa el siguiente fragmento de código

const correctAsync500ms = () => {
  return new Promise(resolve => {
    setTimeout(resolve, 500, 'correct500msResult');
  });
};

const correctAsync100ms = () => {
  return new Promise(resolve => {
    setTimeout(resolve, 100, 'correct100msResult');
  });
};

const rejectAsync100ms = () => {
  return new Promise((resolve, reject) => {
    setTimeout(reject, 100, 'reject100msError');
  });
};

const asyncInArray = async (fun1, fun2) => {
  const label = 'test async functions in array';
  try {
    console.time(label);
    const p1 = fun1();
    const p2 = fun2();
    const result = [await p1, await p2];
    console.timeEnd(label);
  } catch (e) {
    console.error('error is', e);
    console.timeEnd(label);
  }
};

const asyncInPromiseAll = async (fun1, fun2) => {
  const label = 'test async functions with Promise.all';
  try {
    console.time(label);
    let [value1, value2] = await Promise.all([fun1(), fun2()]);
    console.timeEnd(label);
  } catch (e) {
    console.error('error is', e);
    console.timeEnd(label);
  }
};

(async () => {
  console.group('async functions without error');
  console.log('async functions without error: start')
  await asyncInArray(correctAsync500ms, correctAsync100ms);
  await asyncInPromiseAll(correctAsync500ms, correctAsync100ms);
  console.groupEnd();

  console.group('async functions with error');
  console.log('async functions with error: start')
  await asyncInArray(correctAsync500ms, rejectAsync100ms);
  await asyncInPromiseAll(correctAsync500ms, rejectAsync100ms);
  console.groupEnd();
})();

Respuesta #3

Actualización:

La respuesta original dificulta (y en algunos casos hace imposible) manejar correctamente los rechazos de promesas. La solución correcta es usar Promise.all:

const [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);

Respuesta original:

Solo asegúrate de llamar a ambas funciones antes de esperar cualquiera de ellas:

// Call both functions
const somePromise = someCall();
const anotherPromise = anotherCall();

// Await both promises    
const someResult = await somePromise;
const anotherResult = await anotherPromise;

Respuesta #4

Hay otra manera sin Promise.all() de hacerlo en paralelo:

Primero, tenemos 2 funciones para imprimir números:

function printNumber1() {
   return new Promise((resolve,reject) => {
      setTimeout(() => {
      console.log("Number1 is done");
      resolve(10);
      },1000);
   });
}

function printNumber2() {
   return new Promise((resolve,reject) => {
      setTimeout(() => {
      console.log("Number2 is done");
      resolve(20);
      },500);
   });
}

Esto es secuencial:

async function oneByOne() {
   const number1 = await printNumber1();
   const number2 = await printNumber2();
} 
//Output: Number1 is done, Number2 is done

Esto es paralelo:

async function inParallel() {
   const promise1 = printNumber1();
   const promise2 = printNumber2();
   const number1 = await promise1;
   const number2 = await promise2;
}
//Output: Number2 is done, Number1 is done

Respuesta #5

En mi caso, tengo varias tareas que quiero ejecutar en paralelo, pero necesito hacer algo diferente con el resultado de esas tareas.

function wait(ms, data) {
    console.log('Starting task:', data, ms);
    return new Promise(resolve => setTimeout(resolve, ms, data));
}

var tasks = [
    async () => {
        var result = await wait(1000, 'moose');
        // do something with result
        console.log(result);
    },
    async () => {
        var result = await wait(500, 'taco');
        // do something with result
        console.log(result);
    },
    async () => {
        var result = await wait(5000, 'burp');
        // do something with result
        console.log(result);
    }
]

await Promise.all(tasks.map(p => p()));
console.log('done');

Y la salida:

Starting task: moose 1000
Starting task: taco 500
Starting task: burp 5000
taco
moose
burp
done

(async function(){
function wait(ms, data) {
    console.log('Starting task:', data, ms);
    return new Promise(resolve => setTimeout(resolve, ms, data));
}

var tasks = [
    async () => {
        var result = await wait(1000, 'moose');
        // do something with result
        console.log(result);
    },
    async () => {
        var result = await wait(500, 'taco');
        // do something with result
        console.log(result);
    },
    async () => {
        var result = await wait(5000, 'burp');
        // do something with result
        console.log(result);
    }
]

await Promise.all(tasks.map(p => p()));
console.log('done');
})();

Respuesta #6

He creado un gist probando diferentes formas de resolver promesas, con resultados. Puede ser útil ver las opciones que funcionan.

Editar: Contenido del gist según el comentario de Jin Lee

// Simple gist to test parallel promise resolution when using async / await

function promiseWait(time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(true);
    }, time);
});
}


async function test() {
    return [
    await promiseWait(1000),
    await promiseWait(5000),
    await promiseWait(9000),
    await promiseWait(3000),
    ]
}

async function test2() {
    return {
        'aa': await promiseWait(1000),
        'bb': await promiseWait(5000),
        'cc': await promiseWait(9000),
        'dd': await promiseWait(3000),
    }
}

async function test3() {
    return await {
        'aa': promiseWait(1000),
        'bb': promiseWait(5000),
        'cc': promiseWait(9000),
        'dd': promiseWait(3000),
    }
}

async function test4() {
    const p1 =  promiseWait(1000);
    const p2 =  promiseWait(5000);
    const p3 =  promiseWait(9000);
    const p4 =  promiseWait(3000);
    return {
        'aa': await p1,
        'bb': await p2,
        'cc': await p3,
        'dd': await p4,
    };
}

async function test5() {
    return await Promise.all([
                             await promiseWait(1000),
                             await promiseWait(5000),
                             await promiseWait(9000),
                             await promiseWait(3000),
                             ]);
}

async function test6() {
    return await Promise.all([
                             promiseWait(1000),
                             promiseWait(5000),
                             promiseWait(9000),
                             promiseWait(3000),
                             ]);
}

async function test7() {
    const p1 =  promiseWait(1000);
    const p2 =  promiseWait(5000);
    const p3 =  promiseWait(9000);
    return {
        'aa': await p1,
        'bb': await p2,
        'cc': await p3,
        'dd': await promiseWait(3000),
    };
}

let start = Date.now();

test().then((res) => {
    console.log('Test Done, elapsed', (Date.now() - start) / 1000, res);

    start = Date.now();
    test2().then((res) => {
        console.log('Test2 Done, elapsed', (Date.now() - start) / 1000, res);

        start = Date.now();
        test3().then((res) => {
            console.log('Test3 Done, elapsed', (Date.now() - start) / 1000, res);

            start = Date.now();
            test4().then((res) => {
                console.log('Test4 Done, elapsed', (Date.now() - start) / 1000, res);

                start = Date.now();
                test5().then((res) => {
                    console.log('Test5 Done, elapsed', (Date.now() - start) / 1000, res);

                    start = Date.now();
                    test6().then((res) => {
                        console.log('Test6 Done, elapsed', (Date.now() - start) / 1000, res);
                    });

                    start = Date.now();
                    test7().then((res) => {
                        console.log('Test7 Done, elapsed', (Date.now() - start) / 1000, res);
                    });
                });
            });

        });
    });

});
/*
Test Done, elapsed 18.006 [ true, true, true, true ]
Test2 Done, elapsed 18.009 { aa: true, bb: true, cc: true, dd: true }
Test3 Done, elapsed 0 { aa: Promise { <pending> },
  bb: Promise { <pending> },
  cc: Promise { <pending> },
  dd: Promise { <pending> } }
Test4 Done, elapsed 9 { aa: true, bb: true, cc: true, dd: true }
Test5 Done, elapsed 18.008 [ true, true, true, true ]
Test6 Done, elapsed 9.003 [ true, true, true, true ]
Test7 Done, elapsed 12.007 { aa: true, bb: true, cc: true, dd: true }
*/

Artículos relacionados