Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Paralelizando promesas

Lupo Montero
September 13, 2018

Paralelizando promesas

Si necesitamos ejecutar muchas tareas asíncronas (imagina un arreglo de promesas) se convierte en una gran limitación no poder controlar la concurrencia de las operaciones con Promise.all. En esta charla veremos como podemos agregar la noción de concurrencia, throttling y manejo de errores para grupos de tareas modeladas como promesas.

Slides originales: https://docs.google.com/presentation/d/e/2PACX-1vTTXUEP7CCckZ4gefsnLe0DeYIbigf-7WwGrq76_VwTlp9qzV2xeB4gQZjZ2NYehViAeIjnnS09Mw2h/pub?start=false&loop=false&delayms=3000

Presentado en SantiagoJS el 13 Sep 2018: https://www.meetup.com/NodersJS/events/snrpwpyxmbrb/

Blog post en Medium: https://medium.com/laboratoria-developers/paralelizando-promesas-372894c1d21f

Lupo Montero

September 13, 2018
Tweet

More Decks by Lupo Montero

Other Decks in Technology

Transcript

  1. { Error: An internal error has occurred. Raw server response:

    "{"error":{"code":400,"message":"QUOTA_EXCEEDED : Exceeded quota for deleting accounts.","errors":[{"message":"QUOTA_EXCEEDED : Exceeded quota for deleting accounts.","domain":"global","reason":"invalid"}]}}" at FirebaseAuthError.FirebaseError [as constructor] (/Users/lupo/work/lupomontero/paralelizando-promesas/node_modules/firebase-admin/lib/utils/error.js:39:28) at FirebaseAuthError.PrefixedFirebaseError [as constructor] (/Users/lupo/work/lupomontero/paralelizando-promesas/node_modules/firebase-admin/lib/utils/error.js:85:28) at new FirebaseAuthError (/Users/lupo/work/lupomontero/paralelizando-promesas/node_modules/firebase-admin/lib/utils/error.js:143:16) at Function.FirebaseAuthError.fromServerError (/Users/lupo/work/lupomontero/paralelizando-promesas/node_modules/firebase-admin/lib/utils/error.js:173:16) at /Users/lupo/work/lupomontero/paralelizando-promesas/node_modules/firebase-admin/lib/auth/auth-api-request.js:723 :45 at process._tickCallback (internal/process/next_tick.js:68:7) errorInfo: { code: 'auth/internal-error', message: 'An internal error has occurred. Raw server response: "{"error":{"code":400,"message":"QUOTA_EXCEEDED : Exceeded quota for deleting accounts.","errors":[{"message":"QUOTA_EXCEEDED : Exceeded quota for deleting accounts.","domain":"global","reason":"invalid"}]}}"' }, codePrefix: 'auth' } QUOTA_EXCEEDED : Exceeded quota for deleting accounts.
  2. Requerimientos • No más de 10 tareas (invocaciones a auth.deleteUser())

    por segundo. • Batches (grupos) de 10 • Intervalo entre batches (1 seg) • Si una tarea falla las demás deben continuar
  3. Tareas vs promesas const promises = users.map(user => auth.deleteUser(user.localId)) const

    tasks = users.map(user => () => auth.deleteUser(user.localId))
  4. Series const series = (tasks, results = []) => (

    (!tasks.length) ? Promise.resolve(results) : tasks[0]().then(result => series(tasks.slice(1), [...results, result])) );
  5. Dividiendo arreglo en “batches” (grupos?) const splitArrayIntoBatches = (arr, limit)

    => arr.reduce((memo, item) => { if (memo.length && memo[memo.length - 1].length < limit) { memo[memo.length - 1].push(item); return memo; } return [...memo, [item]]; }, []);
  6. const array = ['a', 'b', 'c', 'd', 'e']; splitArrayIntoBatches(array, 1);

    // => [['a'], ['b'], ['c'], ['d'], ['e']] splitArrayIntoBatches(array, 2); // => [['a', 'b'], ['c', 'd'], ['e']] splitArrayIntoBatches(array, 3); // => [['a', 'b', 'c'], ['d', 'e']] splitArrayIntoBatches(array, 4); // => [['a', 'b', 'c', 'd'], ['e']] splitArrayIntoBatches(array, 5); // => [['a', 'b', 'c', 'd', 'e']]
  7. const batched = (tasks, concurrency) => { const processBatches =

    (batches, prevResults = []) => ( (!batches.length) ? Promise.resolve(prevResults) : Promise.all(batches[0].map(fn => fn())) .then(batchResults => processBatches(batches.slice(1), [ ...prevResults, ...batchResults, ])) ); return processBatches(splitArrayIntoBatches(tasks, concurrency)); };
  8. // ejecuta todas las tareas de 5 en 5 batched(tasks,

    5) .then(console.log) .catch(console.error);
  9. Agregando tiempo de espera entre lotes // Antes processBatches(batches.slice(1), results)

    // Después new Promise((resolve, reject) => setTimeout( () => processBatches(batches.slice(1), results) .then(resolve, reject), interval, ))
  10. const throttled = (tasks, concurrency, interval = 0) => {

    const processBatches = (batches, prevResults = []) => { if (!batches.length) { return Promise.resolve(prevResults); } return Promise.all(batches[0].map(fn => fn())).then((batchResults) => { const results = [...prevResults, ...batchResults]; return (batches.length <= 1) ? results : new Promise((resolve, reject) => setTimeout( () => processBatches(batches.slice(1), results).then(resolve, reject), interval, )); }); }; return processBatches(splitArrayIntoBatches(tasks, concurrency)); };
  11. // ejecuta tareas de 5 en 5, // esperando 1s

    entre cada lote throttled(tasks, 5, 1000) .then(console.log) .catch(console.error);
  12. // así procesábamos las tareas de un batch (en paralelo)

    Promise.all( batches[0].map(fn => fn()), ) // agregamos la opción `failFast` y manejamos errores cuando // `failFast` sea `false` Promise.all( batches[0].map(fn => (failFast ? fn() : fn().catch(err => err))), )
  13. const pact = (tasks, concurrency, interval = 0, failFast =

    true) => { const processBatches = (batches, prevResults = []) => { if (!batches.length) { return Promise.resolve(prevResults); } return Promise.all( batches[0].map(fn => (failFast ? fn() : fn().catch(err => err))), ).then((batchResults) => { const results = [...prevResults, ...batchResults]; return (batches.length <= 1) ? results : new Promise((resolve, reject) => setTimeout( () => processBatches(batches.slice(1), results).then(resolve, reject), interval, )); }); }; return processBatches(splitArrayIntoBatches(tasks, concurrency)); };