Saltar al contenido

Promesa de JavaScript ES6 para bucle

Hola, descubrimos la respuesta a tu búsqueda, has scroll y la encontrarás más abajo.

Solución:

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

En segundo lugar, cada promesa que se crea con new Promise debe resolverse con una llamada a resolve (o reject). Esto debe hacerse cuando expire el temporizador. Eso desencadenará cualquier then devolución de llamada que tendría en esa promesa. Y tal then 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 for bucle que comienza con una promesa de resolución inmediata

  2. Con Array#reduce que comienza con una promesa que se resuelve de inmediato

  3. Con una función que se hace pasar por devolución de llamada de resolución

  4. Con ECMAScript2017’s async / await sintaxis

  5. Con ECMAScript2020’s for await...of sintaxis

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

1. Con for

usted pueden utilizar una for bucle, pero debe asegurarse de que no se ejecute new Promise sincrónicamente. En su lugar, crea una promesa inicial que se resuelve de inmediato 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. Tu creas un array con la misma longitud que la cadena que desea ejecutar y comience 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 tengo un array con datos que se utilizarán en las promesas.

3. Con una función que se hace pasar por resolución-devolución de llamada

Aquí creamos una función y la llamamos inmediatamente. Crea la primera promesa de forma sincrónica. Cuando se resuelve, se vuelve a llamar a la función:

(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 puede ver que se llama inmediatamente con el argumento 0. Este es el contador, y el I argumento. La función creará una nueva promesa si ese contador todavía está por debajo de 10; de lo contrario, el encadenamiento se detiene.

La llamada a resolve() activará el then devolución de llamada que llamará a la función nuevamente. 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 como el new Promise() Las llamadas se ejecutan sincrónicamente, pero en realidad el async función devoluciones cuando ejecuta el primer 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 el siguiente, y así continúa hasta que finaliza el ciclo.

Como puede ser algo común devolver una promesa basada en un tiempo de espera, puede crear una función separada para generar dicha promesa. Se llama prometedor 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

Con EcmaScript 2020, el for await...of encontró su camino hacia los motores JavaScript modernos. 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);
)();

Puedes usar async/await para esto. Explicaría más, pero en realidad no hay nada. Es solo un regular for bucle pero agregué el await palabra clave antes de la construcción de su Promesa

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 te da poderes como en La leyenda de Zelda: un vínculo con el pasado donde puedes afectar cosas tanto en el Mundo de la Luz y el mundo oscuro, es decir, puede trabajar fácilmente con 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 IIFE estúpidos.

// 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

Mira cómo no tenemos que lidiar con eso molesto .then llamar dentro de nuestro procedimiento? Y async palabra clave se asegurará automáticamente de que una Promise se devuelve, por lo que podemos encadenar un .then llamar al valor devuelto. Esto nos prepara para un gran éxito: ejecute la secuencia de n Promesas luego hacer algo importante, como mostrar un mensaje de éxito / error.

Basado en la excelente respuesta de trincot, escribí una función reutilizable que acepta un controlador para ejecutar sobre cada elemento en un array. 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á utilizar en muchas situaciones de bucle de promesa.

Código listo para copiar y pegar:

// SEE https://stackoverflow.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 el array para hacer un bucle como el primer argumento y la función del controlador como el segundo. No pase parámetros para el tercer, cuarto y quinto argumento, se utilizan 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 avanzados

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

manejador (actual, índice, todos)

Al controlador se le pasan 3 argumentos. El elemento actual, el índice del elemento actual y el completo array ser enrollado. Si la función del controlador necesita realizar un trabajo asincrónico, 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 esperaba.

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 rompen cuando se produce 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 de captura a cualquier bucle interno e invoque la función de rechazo cuando suceda. 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.

para-asíncrono

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
  )
)

Consulte el archivo Léame del paquete para obtener más detalles.

Acuérdate de que tienes autorización de valorar esta sección si te ayudó.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *