Combinaison de fonction asynchrone + attente + setTimeout

307

J'essaie d'utiliser les nouvelles fonctionnalités asynchrones et j'espère que la résolution de mon problème aidera les autres à l'avenir. Voici mon code qui fonctionne:

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

Le problème est que ma boucle while s'exécute trop rapidement et que le script envoie trop de requêtes par seconde à l'API Google. Par conséquent, je voudrais créer une fonction de veille qui retarde la demande. Ainsi, je pourrais également utiliser cette fonction pour retarder d'autres requêtes. S'il existe un autre moyen de retarder la demande, veuillez m'en informer.

Quoi qu'il en soit, c'est mon nouveau code qui ne fonctionne pas. La réponse de la demande est renvoyée à la fonction asynchrone anonyme dans le setTimeout, mais je ne sais pas comment je peux retourner la réponse à la fonction sleep resp. à la fonction asyncGenerator initiale.

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

J'ai déjà essayé quelques options: stockage de la réponse dans une variable globale et retour de la fonction sleep, rappel dans la fonction anonyme, etc.

JShinigami
la source

Réponses:

615

Votre sleepfonction ne fonctionne pas car setTimeoutne renvoie pas (encore?) Une promesse qui pourrait être awaitéditée. Vous devrez le promettre manuellement:

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

Btw, pour ralentir votre boucle, vous ne voulez probablement pas utiliser une sleepfonction qui prend un rappel et la diffère comme ceci. Je recommanderais plutôt de faire quelque chose comme

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

ce qui permet au calcul de parentsprendre au moins 5 secondes.

Bergi
la source
11
J'adore l' Promise.allapproche. Si simple et élégant!
Anshul Koka
4
que var [parents]représente la notation de ? Je ne l'ai jamais vu auparavant et c'est une chose difficile à google
natedog
6
@NateUsher C'est la déstructuration de la baie
Bergi
1
@tinkerr "le délai d'attente doit être déclaré asynchrone s'il doit être attendu " - Non. Une fonction n'a besoin que de renvoyer une promesse qui peut être attendue (ou en fait, une table est suffisante). La façon dont il y parvient dépend de la mise en œuvre de la fonction, il n'est pas nécessaire que ce soit un async function.
Bergi
2
@naisanza Non, async/ awaitest basé sur des promesses. La seule chose qu'il remplace, ce sont les thenappels.
Bergi
152

Depuis Node 7.6 , vous pouvez combiner la fonction de promisifyfonctions du module utils avec setTimeout().

Node.js

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

Javascript

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

Usage

(async () => {
    console.time("Slept for")
    await sleep(3000)
    console.timeEnd("Slept for")
})()
Harry
la source
1
Dans nodeJS await require('util').promisify(setTimeout)(3000)peut également être réalisé sans nécessiter par:await setTimeout[Object.getOwnPropertySymbols(setTimeout)[0]](3000)
Shl
5
Intéressant @Shl. Je pense que c'est moins lisible que ma solution. Si les gens ne sont pas d'accord, je peux l'ajouter à la solution?
Harry
2
La version requise est clairement bien meilleure que la getOwnPropertySymbolsversion ... si elle n'est pas cassée ...!
Matt Fletcher
2
Salut, @Harry. Il semble que vous ayez incorporé la doublure de la réponse de FlavorScape dans votre propre réponse. Je ne veux pas présumer de vos intentions, mais ce n'est pas vraiment juste pour eux. Pourriez-vous annuler votre modification? En ce moment ça ressemble un peu au plagiat ..
Félix Gagnon-Grenier
2
J'ai supprimé le one-liner car la réponse est juste en dessous, mais j'ai vu de nombreuses réponses populaires mettre à jour leurs réponses pour inclure d'autres nouvelles réponses car la plupart des lecteurs ne prennent pas la peine de regarder au-delà des premières réponses.
Harry
130

La doublure rapide, en ligne

 await new Promise(resolve => setTimeout(resolve, 1000));
FlavorScape
la source
4
let sleep = ms => new Promise( r => setTimeout(r, ms));// une fonction de doublure
Soldeplata Saketos
8
encore plus court :-)await new Promise(resolve => setTimeout(resolve, 5000))
Liran Brimer
1
qu'est-ce que cela signifie lorsque vous utilisez "résoudre" x 2 fois sur la même ligne? Comme: attendre une nouvelle promesse (résoudre => setTimeout (résoudre, 1000)); fait-il réf. à lui-même ou quoi? Je ferais quelque chose comme ça à la place: function myFunc () {}; attendre une nouvelle promesse (résoudre => setTimeout (myFunc, 1000));
PabloDK
35

setTimeoutn'est pas une asyncfonction, vous ne pouvez donc pas l'utiliser avec ES7 async-wait. Mais vous pouvez implémenter votre sleepfonction en utilisant ES6 Promise :

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

Ensuite, vous pourrez utiliser cette nouvelle sleepfonction avec ES7 async-wait:

var fileList = await sleep(listFiles, nextPageToken)

Veuillez noter que je ne réponds qu'à votre question sur la combinaison async / ES7 avec ES7 setTimeout, bien que cela ne puisse pas résoudre votre problème avec l'envoi de trop de demandes par seconde.


Mise à jour: les versions modernes de node.js ont une implémentation de délai d'attente asynchrone intégrée , accessible via l' aide util.promisify :

const {promisify} = require('util');
const setTimeoutAsync = promisify(setTimeout);
Leonid Beschastny
la source
2
Vous ne devriez pas faire ça, quand vous fnlancez l'erreur, vous ne serez pas pris.
Bergi
@Bergi Je pense que ça bouillonne jusqu'à l' new Promiseendroit où vous le pouvez sleep.catch.
Florian Wendelborn
3
@Dodekeract Non, c'est dans un setTimeoutrappel asynchrone et le new Promiserappel est fait depuis longtemps. Il bouillonnera dans le contexte mondial et sera levé comme une exception non gérée.
Bergi
> problème avec l'envoi de trop de requêtes par seconde. Vous voulez peut-être utiliser "anti-rebond" pour empêcher des choses comme l'interface utilisateur de tirer trop de ruptures.
FlavorScape
5

Si vous souhaitez utiliser le même type de syntaxe que setTimeoutvous pouvez écrire une fonction d'aide comme celle-ci:

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

Vous pouvez alors l'appeler ainsi:

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

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

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

doStuffAsync();

J'ai fait un résumé: https://gist.github.com/DaveBitter/f44889a2a52ad16b6a5129c39444bb57

Dave Bitter
la source
1
un nom de fonction comme delayRunaurait plus de sens ici, car il retardera l'exécution de la fonction de rappel de X secondes. Pas un exemple très attendu, 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
la source
0

Le code suivant fonctionne dans Chrome et Firefox et peut-être dans d'autres navigateurs.

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

Mais dans Internet Explorer, j'obtiens une erreur de syntaxe pour le "(resolve **=>** setTimeout..."

Shadowned
la source
0

Faites un utilitaire inspiré de la réponse de Dave

Fondamentalement passé dans un donerappel à appeler lorsque l'opération est terminée.

// 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);
});

Voici comment je l'utilise:

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
la source
0

Ceci est ma version avec nodejs maintenant en 2020 dans 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
la source
-3

Il s'agit d'une solution plus rapide dans une doublure.

J'espère que cela vous aidera.

// WAIT FOR 200 MILISECONDS TO GET DATA //
await setTimeout(()=>{}, 200);
Rommy Garg
la source
1
Ça ne marche pas. Ceci: await setTimeout(()=>{console.log('first')}, 200); console.log ('second')imprime en deuxième puis en premier
gregn3
1
@ gregn3 c'est le point oui. Il s'agit d'une solution non bloquante où le code en dehors de la fonction peut continuer à s'exécuter pendant qu'une "opération de blocage" est terminée en dehors du flux de programme principal. Bien que la syntaxe que vous et Rommy et Mohamad avez fournie n'est pas strictement correcte en raison de la nécessité d'attendre pour être rappé dans une fonction asynchrone (peut-être un ajout assez récent), j'utilise également node.js. Ceci est ma solution modifiée. var test = async () => { await setTimeout(()=>{console.log('first')}, 1000); console.log ('second') }J'ai prolongé le délai d'expiration pour montrer son utilité.
azariah