Combinación de función asíncrona + espera + setTimeout

307

Estoy tratando de usar las nuevas funciones asíncronas y espero que resolver mi problema ayude a otros en el futuro. Este es mi código que está funcionando:

  async function asyncGenerator() {
    // other code
    while (goOn) {
      // other code
      var fileList = await listFiles(nextPageToken);
      var parents = await requestParents(fileList);
      // other code
    }
    // other code
  }

  function listFiles(token) {
    return gapi.client.drive.files.list({
      'maxResults': sizeResults,
      'pageToken': token,
      'q': query
    });
  }

El problema es que mi ciclo while se ejecuta demasiado rápido y el script envía demasiadas solicitudes por segundo a la API de Google. Por lo tanto, me gustaría construir una función de suspensión que retrase la solicitud. Por lo tanto, también podría usar esta función para retrasar otras solicitudes. Si hay otra forma de retrasar la solicitud, hágamelo saber.

De todos modos, este es mi nuevo código que no funciona. La respuesta de la solicitud se devuelve a la función asincrónica anónima dentro de setTimeout, pero simplemente no sé cómo puedo devolver la respuesta a la función de suspensión resp. a la función inicial asyncGenerator.

  async function asyncGenerator() {
    // other code
    while (goOn) {
      // other code
      var fileList = await sleep(listFiles, nextPageToken);
      var parents = await requestParents(fileList);
      // other code
    }
    // other code
  }

  function listFiles(token) {
    return gapi.client.drive.files.list({
      'maxResults': sizeResults,
      'pageToken': token,
      'q': query
    });
  }

  async function sleep(fn, par) {
    return await setTimeout(async function() {
      await fn(par);
    }, 3000, fn, par);
  }

Ya he probado algunas opciones: almacenar la respuesta en una variable global y devolverla desde la función de suspensión, devolución de llamada dentro de la función anónima, etc.

JShinigami
fuente

Respuestas:

615

Su sleepfunción no funciona porque setTimeoutno (¿todavía?) Devuelve una promesa que podría ser awaiteditada. Tendrá que prometerlo manualmente:

function timeout(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
async function sleep(fn, ...args) {
    await timeout(3000);
    return fn(...args);
}

Por cierto, para ralentizar su bucle, probablemente no desee usar una sleepfunción que tome una devolución de llamada y la difiera de esta manera. Prefiero recomendar hacer algo como

while (goOn) {
  // other code
  var [parents] = await Promise.all([
      listFiles(nextPageToken).then(requestParents),
      timeout(5000)
  ]);
  // other code
}

lo que permite que el cálculo parentstome al menos 5 segundos.

Bergi
fuente
11
Me encanta el Promise.allenfoque. ¡Tan simple y elegante!
Anshul Koka
44
¿Qué var [parents]representa la notación de ? No lo he visto antes y es algo difícil de
googlear
66
@NateUsher Es la desestructuración de
Bergi
1
@tinkerr " necesidades de tiempo de espera para ser declaradas asíncrono si necesita ser esperado " - Nop. Una función solo necesita devolver una promesa que se puede esperar (o, de hecho, un thenable es suficiente). Cómo se logra eso depende de la implementación de la función, no es necesario que sea un async function.
Bergi el
2
@naisanza No, async/ awaitse basa en promesas. Lo único que reemplaza son las thenllamadas.
Bergi
152

Desde el Nodo 7.6 , puede combinar la función de promisifyfunciones desde el módulo utils con setTimeout().

Node.js

const sleep = require('util').promisify(setTimeout)

Javascript

const sleep = m => new Promise(r => setTimeout(r, m))

Uso

(async () => {
    console.time("Slept for")
    await sleep(3000)
    console.timeEnd("Slept for")
})()
Harry
fuente
1
En nodeJS await require('util').promisify(setTimeout)(3000)también se puede lograr sin necesidad de:await setTimeout[Object.getOwnPropertySymbols(setTimeout)[0]](3000)
Shl
55
Interesante @Shl. Sin embargo, creo que es menos legible que mi solución. Si la gente no está de acuerdo, ¿puedo agregarlo a la solución?
Harry
2
La versión requerida es claramente mucho mejor que la getOwnPropertySymbolsversión ... ¡si no está rota ...!
Matt Fletcher el
2
Hola, @Harry. Parece que incorporó el único revestimiento de la respuesta de FlavorScape en su propia respuesta. No quiero presumir de tus intenciones, pero eso no es realmente justo para ellos. ¿Podrías revertir tu edición? En este momento parece un plagio ..
Félix Gagnon-Grenier
2
He eliminado la línea única ya que la respuesta está justo debajo, sin embargo, he visto muchas respuestas populares actualizar sus respuestas para incluir otras respuestas nuevas, ya que la mayoría de los lectores no se molestan en mirar más allá de las primeras respuestas.
Harry
130

La forma rápida en línea

 await new Promise(resolve => setTimeout(resolve, 1000));
FlavorScape
fuente
44
let sleep = ms => new Promise( r => setTimeout(r, ms));// una función de una línea
Soldeplata Saketos
8
aún más corto :-)await new Promise(resolve => setTimeout(resolve, 5000))
Liran Brimer
1
¿Qué significa cuando ustedes usan "resolver" x 2 veces en la misma línea? Me gusta: aguardar nueva promesa (resolve => setTimeout (resolve, 1000)); lo hace ref. para sí mismo o qué? En cambio, haría algo como esto: function myFunc () {}; esperar nueva promesa (resolve => setTimeout (myFunc, 1000));
PabloDK
35

setTimeoutno es una asyncfunción, por lo que no puede usarla con ES7 async-await. Pero podría implementar su sleepfunción utilizando ES6 Promise :

function sleep (fn, par) {
  return new Promise((resolve) => {
    // wait 3s before calling fn(par)
    setTimeout(() => resolve(fn(par)), 3000)
  })
}

Entonces podrá utilizar esta nueva sleepfunción con ES7 async-await:

var fileList = await sleep(listFiles, nextPageToken)

Tenga en cuenta que solo estoy respondiendo su pregunta sobre la combinación de ES7 async / wait con setTimeout, aunque puede que no ayude a resolver su problema al enviar demasiadas solicitudes por segundo.


Actualización: las versiones modernas de node.js tienen una implementación de tiempo de espera asíncrono incorporado , accesible a través de util.promisify helper:

const {promisify} = require('util');
const setTimeoutAsync = promisify(setTimeout);
Leonid Beschastny
fuente
2
No deberías hacer eso, cuando fnlanza el error no sería atrapado.
Bergi
@ Bergi, creo que burbujea hasta new Promisedonde puedes sleep.catchhacerlo.
Florian Wendelborn
3
@Dodekeract No, está en una setTimeoutdevolución de llamada asincrónica y la new Promisedevolución de llamada se ha realizado durante mucho tiempo. Burbujeará al contexto global y se lanzará como una excepción no controlada.
Bergi
> problema con el envío de demasiadas solicitudes por segundo. Desea usar "rebotar" tal vez para evitar que cosas como UI disparen demasiados ruquests.
FlavorScape
5

Si desea utilizar el mismo tipo de sintaxis, setTimeoutpuede escribir una función auxiliar como esta:

const setAsyncTimeout = (cb, timeout = 0) => new Promise(resolve => {
    setTimeout(() => {
        cb();
        resolve();
    }, timeout);
});

Luego puede llamarlo así:

const doStuffAsync = async () => {
    await setAsyncTimeout(() => {
        // Do stuff
    }, 1000);

    await setAsyncTimeout(() => {
        // Do more stuff
    }, 500);

    await setAsyncTimeout(() => {
        // Do even more stuff
    }, 2000);
};

doStuffAsync();

Hice una idea esencial: https://gist.github.com/DaveBitter/f44889a2a52ad16b6a5129c39444bb57

Dave Bitter
fuente
1
un nombre de función como delayRuntendría más sentido aquí, ya que retrasará la ejecución de la función de devolución de llamada en X segundos. No es un ejemplo muy esperado, OMI.
mix3d
2
var testAwait = function () {
    var promise = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('Inside test await');
        }, 1000);
    });
    return promise;
}

var asyncFunction = async function() {
    await testAwait().then((data) => {
        console.log(data);
    })
    return 'hello asyncFunction';
}

asyncFunction().then((data) => {
    console.log(data);
});

//Inside test await
//hello asyncFunction
vignesh
fuente
0

El siguiente código funciona en Chrome y Firefox y quizás en otros navegadores.

function timeout(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
async function sleep(fn, ...args) {
    await timeout(3000);
    return fn(...args);
}

Pero en Internet Explorer obtengo un error de sintaxis para "(resolve **=>** setTimeout..."

Sombreado
fuente
0

Hizo una utilidad inspirada en la respuesta de Dave

Básicamente pasado en una donedevolución de llamada para llamar cuando finaliza la operación.

// Function to timeout if a request is taking too long
const setAsyncTimeout = (cb, timeout = 0) => new Promise((resolve, reject) => {
  cb(resolve);
  setTimeout(() => reject('Request is taking too long to response'), timeout);
});

Así es como lo uso:

try {
  await setAsyncTimeout(async done => {
    const requestOne = await someService.post(configs);
    const requestTwo = await someService.get(configs);
    const requestThree = await someService.post(configs);
    done();
  }, 5000); // 5 seconds max for this set of operations
}
catch (err) {
  console.error('[Timeout] Unable to complete the operation.', err);
}
Jee Mok
fuente
0

Esta es mi versión con nodejs ahora en 2020 en AWS labdas

const sleep = require('util').promisify(setTimeout)

async function f1 (some){
...
}

async function f2 (thing){
...
}

module.exports.someFunction = async event => {
    ...
    await f1(some)
    await sleep(5000)
    await f2(thing)
    ...
}
zwitterion
fuente
-3

Esta es una solución más rápida en una sola línea.

Espero que esto ayude.

// WAIT FOR 200 MILISECONDS TO GET DATA //
await setTimeout(()=>{}, 200);
Rommy Garg
fuente
1
No funciona Esto: await setTimeout(()=>{console.log('first')}, 200); console.log ('second')imprime segundo y luego primero
gregn3
1
@ gregn3 ese es el punto sí. Esta es una solución sin bloqueo donde el código fuera de la función puede continuar ejecutándose mientras se completa una "operación de bloqueo" fuera del flujo del programa principal. Aunque la sintaxis que usted, Rommy y Mohamad han proporcionado no es estrictamente correcta debido al requisito de que se espere una función asíncrona en espera (podría ser una adición bastante reciente), también estoy usando node.js. Esta es mi solución ajustada. var test = async () => { await setTimeout(()=>{console.log('first')}, 1000); console.log ('second') }He extendido el tiempo de espera para mostrar su utilidad.
azariah