then sincronas promises promesas funciones ejemplo await async asincronia anidadas javascript es6-promise

javascript - sincronas - promesas typescript



Promesa de JavaScript ES6 para el bucle (5)

Aquí está mi valor de 2 centavos:

  • función forpromise()
  • emula un clásico para bucle
  • permite una salida anticipada basada en la lógica interna, devolviendo un valor
  • puede recopilar una serie de resultados pasados ​​a resolve / next / collect
  • el valor predeterminado es inicio = 0, incremento = 1
  • las excepciones lanzadas dentro del bucle se capturan y se pasan a .catch ()

let promises = []; students.map((student, index) => { student.rollNo = index + 1; student.city = ''City Name''; //Update whatever information on student you want promises.push(student.save()); //where save() is a function used to save data in mongoDB }); Promise.all(promises).then(() => { //All the save queries will be executed when .then is executed //You can do further operations here after as all update operations are completed now });

Esta pregunta ya tiene una respuesta aquí:

for (let i = 0; i < 10; i++) { const promise = new Promise((resolve, reject) => { const timeout = Math.random() * 1000; setTimeout(() => { console.log(i); }, timeout); }); // TODO: Chain this promise to the previous one (maybe without having it running?) }

Lo anterior dará el siguiente resultado aleatorio:

6 9 4 8 5 1 7 2 3 0

La tarea es simple: asegúrese de que cada promesa se ejecute solo después de la otra ( .then() ).

Por alguna razón, no pude encontrar una manera de hacerlo.

Probé las funciones del generador ( yield ), probé funciones simples que devuelven una promesa, pero al final del día siempre se trata del mismo problema: el bucle es sincrónico .

Con async simplemente usaría async.series() .

¿Cómo lo resuelves?


Basado en la excelente respuesta de trincot, escribí una función reutilizable que acepta un controlador para ejecutar cada elemento en una matriz. La función en sí misma devuelve una promesa que le permite esperar hasta que el ciclo haya terminado y la función de controlador que pasa también puede devolver una promesa.

bucle (elementos, controlador): promesa

Me tomó algo de tiempo hacerlo bien, pero creo que el siguiente código se podrá usar en muchas situaciones de promesa.

Código listo para copiar y pegar:

// SEE https://.com/a/46295049/286685 const loop = (arr, fn, busy, err, i=0) => { const body = (ok,er) => { try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)} catch(e) {er(e)} } const next = (ok,er) => () => loop(arr, fn, ok, er, ++i) const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok() return busy ? run(busy,err) : new Promise(run) }

Uso

Para usarlo, llámelo con la matriz para recorrer como primer argumento y la función de controlador como segundo. No pase parámetros para los argumentos tercero, cuarto y quinto, se usan internamente.

const loop = (arr, fn, busy, err, i=0) => { const body = (ok,er) => { try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)} catch(e) {er(e)} } const next = (ok,er) => () => loop(arr, fn, ok, er, ++i) const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok() return busy ? run(busy,err) : new Promise(run) } const items = [''one'', ''two'', ''three''] loop(items, item => { console.info(item) }) .then(() => console.info(''Done!''))

Casos de uso avanzado

Veamos la función del controlador, los bucles anidados y el manejo de errores.

manejador (actual, índice, todo)

El controlador obtiene 3 argumentos. El elemento actual, el índice del elemento actual y la matriz completa que se está pasando. Si la función del controlador necesita hacer un trabajo asíncrono, puede devolver una promesa y la función de bucle esperará a que la promesa se resuelva antes de comenzar la siguiente iteración. Puede anidar invocaciones de bucle y todo funciona como se espera.

const loop = (arr, fn, busy, err, i=0) => { const body = (ok,er) => { try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)} catch(e) {er(e)} } const next = (ok,er) => () => loop(arr, fn, ok, er, ++i) const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok() return busy ? run(busy,err) : new Promise(run) } const tests = [ [], [''one'', ''two''], [''A'', ''B'', ''C''] ] loop(tests, (test, idx, all) => new Promise((testNext, testFailed) => { console.info(''Performing test '' + idx) return loop(test, (testCase) => { console.info(testCase) }) .then(testNext) .catch(testFailed) })) .then(() => console.info(''All tests done''))

Manejo de errores

Muchos ejemplos de bucles de promesa que miré se descomponen cuando ocurre una excepción. Conseguir que esta función hiciera lo correcto fue bastante complicado, pero por lo que puedo decir, está funcionando ahora. Asegúrese de agregar un controlador catch a cualquier bucle interno e invoque la función de rechazo cuando ocurra. P.ej:

const loop = (arr, fn, busy, err, i=0) => { const body = (ok,er) => { try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)} catch(e) {er(e)} } const next = (ok,er) => () => loop(arr, fn, ok, er, ++i) const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok() return busy ? run(busy,err) : new Promise(run) } const tests = [ [], [''one'', ''two''], [''A'', ''B'', ''C''] ] loop(tests, (test, idx, all) => new Promise((testNext, testFailed) => { console.info(''Performing test '' + idx) loop(test, (testCase) => { if (idx == 2) throw new Error() console.info(testCase) }) .then(testNext) .catch(testFailed) // <--- DON''T FORGET!! })) .then(() => console.error(''Oops, test should have failed'')) .catch(e => console.info(''Succesfully caught error: '', e)) .then(() => console.info(''All tests done''))

ACTUALIZACIÓN: paquete NPM

Desde que escribí esta respuesta, convertí el código anterior en un paquete NPM.

for-async

Instalar en pc

npm install --save for-async

Importar

var forAsync = require(''for-async''); // Common JS, or import forAsync from ''for-async'';

Uso (asíncrono)

var arr = [''some'', ''cool'', ''array'']; forAsync(arr, function(item, idx){ return new Promise(function(resolve){ setTimeout(function(){ console.info(item, idx); // Logs 3 lines: `some 0`, `cool 1`, `array 2` resolve(); // <-- signals that this iteration is complete }, 25); // delay 25 ms to make async }) })

Vea el archivo Léame para más detalles.


Como ya insinuó en su pregunta, su código crea todas las promesas sincrónicamente. En cambio, solo deben crearse en el momento en que se resuelva el anterior.

En segundo lugar, cada promesa que se crea con una new Promise debe resolverse con un llamado a resolve (o reject ). Esto debe hacerse cuando expire el temporizador. Eso activará cualquier devolución de llamada que tenga en esa promesa. Y tal devolución de llamada (o await ) es una necesidad para implementar la cadena.

Con esos ingredientes, hay varias formas de realizar este encadenamiento asincrónico:

  1. Con un bucle for que comienza con una promesa de resolución inmediata

  2. Con Array#reduce eso comienza con una promesa de resolución inmediata

  3. Con una función que se transmite como devolución de llamada de resolución

  4. Con la sintaxis async / en await ECMAScript2017

  5. Con el ECMAScript2020 propuesto for await...of sintaxis

Vea un fragmento y comentarios para cada una de estas opciones a continuación.

1. Con for

Puede usar un bucle for , pero debe asegurarse de que no se ejecute new Promise sincrónicamente. En su lugar, crea una promesa inicial de resolución inmediata y luego encadena nuevas promesas a medida que se resuelven las anteriores:

for (let i = 0, p = Promise.resolve(); i < 10; i++) { p = p.then(_ => new Promise(resolve => setTimeout(function () { console.log(i); resolve(); }, Math.random() * 1000) )); }

2. Con reduce

Este es solo un enfoque más funcional de la estrategia anterior. Crea una matriz con la misma longitud que la cadena que desea ejecutar y comienza con una promesa de resolución inmediata:

[...Array(10)].reduce( (p, _, i) => p.then(_ => new Promise(resolve => setTimeout(function () { console.log(i); resolve(); }, Math.random() * 1000) )) , Promise.resolve() );

Esto probablemente sea más útil cuando en realidad tiene una matriz con datos para usar en las promesas.

3. Con una función que se pasa a sí misma como devolución de llamada de resolución

Aquí creamos una función y la llamamos de inmediato. Crea la primera promesa sincrónicamente. Cuando se resuelve, la función se llama nuevamente:

(function loop(i) { if (i < 10) new Promise((resolve, reject) => { setTimeout( () => { console.log(i); resolve(); }, Math.random() * 1000); }).then(loop.bind(null, i+1)); })(0);

Esto crea una función llamada loop , y al final del código se puede ver que se llama inmediatamente con el argumento 0. Este es el contador y el argumento i . La función creará una nueva promesa si ese contador aún está por debajo de 10, de lo contrario, el encadenamiento se detiene.

La llamada a resolve() activará la devolución de llamada, que volverá a llamar a la función. loop.bind(null, i+1) es solo una forma diferente de decir _ => loop(i+1) .

4. Con async / await

Los motores JS modernos admiten esta sintaxis :

(async function loop() { for (let i = 0; i < 10; i++) { await new Promise(resolve => setTimeout(resolve, Math.random() * 1000)); console.log(i); } })();

Puede parecer extraño, ya que parece que las new Promise() llamadas new Promise() se ejecutan sincrónicamente, pero en realidad la función async regresa cuando ejecuta la primera await . Cada vez que se resuelve una promesa esperada, el contexto de ejecución de la función se restaura y continúa después de la await , hasta que encuentra la siguiente, y así continúa hasta que finaliza el ciclo.

Como puede ser una cosa común devolver una promesa basada en un tiempo de espera, podría crear una función separada para generar dicha promesa. Esto se llama promisificar una función, en este caso setTimeout . Puede mejorar la legibilidad del código:

const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); (async function loop() { for (let i = 0; i < 10; i++) { await delay(Math.random() * 1000); console.log(i); } })();

5. Con for await...of

Incluso más recientemente, la sintaxis for await...of llegó a algunos motores JavaScript. Aunque realmente no reduce el código en este caso, permite aislar la definición de la cadena de intervalo aleatorio de la iteración real de la misma:

const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); async function * randomDelays(count ,max) { for (let i = 0; i < count; i++) yield delay(Math.random() * max).then(() => i); } (async function loop() { for await (let i of randomDelays(10, 1000)) console.log(i); })();


Puede usar async/await para esto. Explicaría más, pero no hay nada realmente. Es solo un ciclo for regular, pero agregué la palabra clave await antes de la construcción de su Promise

Lo que me gusta de esto es que su Promesa puede resolver un valor normal en lugar de tener un efecto secundario como su código (u otras respuestas aquí) incluyen. Esto le da poderes como en The Legend of Zelda: A Link to the Past, donde puede afectar las cosas tanto en el mundo de la luz como en el mundo oscuro, es decir, puede trabajar fácilmente con los datos antes / después de que los datos prometidos estén disponibles sin tener que recurrir a funciones profundamente anidadas, otras estructuras de control difíciles de manejar o estúpidos IIFE .

// where DarkWorld is in the scary, unknown future // where LightWorld is the world we saved from Ganondorf LightWorld ... await DarkWorld

Así que así es como se verá ...

const someProcedure = async n => { for (let i = 0; i < n; i++) { const t = Math.random() * 1000 const x = await new Promise(r => setTimeout(r, t, i)) console.log (i, x) } return ''done'' } someProcedure(10).then(x => console.log(x)) // => Promise // 0 0 // 1 1 // 2 2 // 3 3 // 4 4 // 5 5 // 6 6 // 7 7 // 8 8 // 9 9 // done

¿Ves cómo no tenemos que lidiar con ese molesto. .then llama dentro de nuestro procedimiento? Y la palabra clave async garantizará automáticamente que se devuelva una Promise , de modo que podamos encadenar una llamada .then en el valor devuelto. Esto nos prepara para un gran éxito: ejecute la secuencia de n Promesas, luego haga algo importante, como mostrar un mensaje de éxito / error.


Si está limitado a ES6, la mejor opción es Promesa de todo. Promise.all(array) también devuelve una serie de promesas después de ejecutar con éxito todas las promesas en el argumento de array . Supongamos que si desea actualizar muchos registros de estudiantes en la base de datos, el siguiente código demuestra el concepto de Promise.

function forpromise(lo, hi, st, res, fn) { if (typeof res === ''function'') { fn = res; res = undefined; } if (typeof hi === ''function'') { fn = hi; hi = lo; lo = 0; st = 1; } if (typeof st === ''function'') { fn = st; st = 1; } return new Promise(function(resolve, reject) { (function loop(i) { if (i >= hi) return resolve(res); const promise = new Promise(function(nxt, brk) { try { fn(i, nxt, brk); } catch (ouch) { return reject(ouch); } }); promise. catch (function(brkres) { hi = lo - st; resolve(brkres) }).then(function(el) { if (res) res.push(el); loop(i + st) }); })(lo); }); } //no result returned, just loop from 0 thru 9 forpromise(0, 10, function(i, next) { console.log("iterating:", i); next(); }).then(function() { console.log("test result 1", arguments); //shortform:no result returned, just loop from 0 thru 4 forpromise(5, function(i, next) { console.log("counting:", i); next(); }).then(function() { console.log("test result 2", arguments); //collect result array, even numbers only forpromise(0, 10, 2, [], function(i, collect) { console.log("adding item:", i); collect("result-" + i); }).then(function() { console.log("test result 3", arguments); //collect results, even numbers, break loop early with different result forpromise(0, 10, 2, [], function(i, collect, break_) { console.log("adding item:", i); if (i === 8) return break_("ending early"); collect("result-" + i); }).then(function() { console.log("test result 4", arguments); // collect results, but break loop on exception thrown, which we catch forpromise(0, 10, 2, [], function(i, collect, break_) { console.log("adding item:", i); if (i === 4) throw new Error("failure inside loop"); collect("result-" + i); }).then(function() { console.log("test result 5", arguments); }). catch (function(err) { console.log("caught in test 5:[Error ", err.message, "]"); }); }); }); }); });

Map es solo un método de ejemplo para loop. También puede usar for or forin o forEach loop. Entonces, el concepto es bastante simple, inicie el ciclo en el que desea realizar operaciones asincrónicas masivas. Empuje cada declaración de operación asíncrona en una matriz declarada fuera del alcance de ese bucle. Una vez que se completa el ciclo, ejecute la declaración Promesa de todos con la matriz preparada de consultas / promesas como argumento.

El concepto básico es que el bucle de JavaScript es síncrono, mientras que la llamada a la base de datos es asíncrona y utilizamos el método push en el bucle que también está sincronizado. Entonces, el problema del comportamiento asincrónico no ocurre dentro del ciclo.