Combinazione di funzione asincrona + await + setTimeout


307

Sto cercando di utilizzare le nuove funzionalità asincrone e spero che risolvere il mio problema possa aiutare gli altri in futuro. Questo è il mio codice che funziona:

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

Il problema è che il mio ciclo while è troppo veloce e lo script invia troppe richieste al secondo all'API di google. Pertanto, vorrei creare una funzione sleep che ritardi la richiesta. Quindi potrei anche usare questa funzione per ritardare altre richieste. Se esiste un altro modo per ritardare la richiesta, per favore fatemelo sapere.

Comunque, questo è il mio nuovo codice che non funziona. La risposta della richiesta viene restituita alla funzione asincrona anonima all'interno di setTimeout, ma non so come restituire la risposta alla funzione sleep. alla funzione iniziale 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);
  }

Ho già provato alcune opzioni: memorizzare la risposta in una variabile globale e restituirla dalla funzione sleep, richiamata all'interno della funzione anonima, ecc.

Risposte:


615

La tua sleepfunzione non funziona perché setTimeoutnon (ancora?) Restituisce una promessa che potrebbe essere modificata await. Dovrai promuoverlo manualmente:

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

A proposito, per rallentare il tuo loop probabilmente non vorrai usare una sleepfunzione che accetta un callback e lo difende in questo modo. Preferirei raccomandare di fare qualcosa del genere

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

che consente al calcolo di parentsrichiedere almeno 5 secondi.


11
Adoro l' Promise.allapproccio. Così semplice ed elegante!
Anshul Koka,

4
cosa var [parents]rappresenta la notazione di ? Non l'ho mai visto prima ed è una cosa difficile per google
natedog

6
@NateUsher È una matrice che distrugge
Bergi il

1
@tinkerr "il timeout deve essere dichiarato asincrono se deve essere atteso " - No. Una funzione deve solo restituire una promessa che può essere attesa (o in realtà, è sufficiente un seguito). Come raggiunge ciò fino all'implementazione della funzione, non deve essere un async function.
Bergi,

2
@naisanza No, async/ awaitsi basa sulle promesse. L'unica cosa che sostituisce sono le thenchiamate.
Bergi,

152

Dal Nodo 7.6 , è possibile combinare la funzione promisifyfunzioni dal modulo 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")
})()

1
In nodeJS await require('util').promisify(setTimeout)(3000)può anche essere ottenuto senza richiedere:await setTimeout[Object.getOwnPropertySymbols(setTimeout)[0]](3000)
Shl

5
Interessante @Shl. Penso che sia meno leggibile della mia soluzione però. Se le persone non sono d'accordo posso aggiungerlo alla soluzione?
Harry

2
La versione richiesta è chiaramente molto meglio della getOwnPropertySymbolsversione ... se non è rotta ...!
Matt Fletcher,

2
Ciao a tutti @ Harry. Sembra che tu abbia incorporato un solo liner della risposta di FlavorScape nella tua risposta. Non voglio presumere delle tue intenzioni, ma non è proprio giusto per loro. Potresti ripristinare la modifica? In questo momento sembra un po 'di plagio ...
Félix Gagnon-Grenier,

2
Ho rimosso il one-liner poiché la risposta è proprio sotto, tuttavia ho visto molte risposte popolari aggiornare le loro risposte per includere altre nuove risposte poiché la maggior parte dei lettori non si preoccupa di guardare oltre le prime risposte.
Harry,

130

Il modo rapido in una linea, in linea

 await new Promise(resolve => setTimeout(resolve, 1000));

4
let sleep = ms => new Promise( r => setTimeout(r, ms));// a one liner function
Soldeplata Saketos,

8
ancora più breve :-)await new Promise(resolve => setTimeout(resolve, 5000))
Liran Brimer il

1
cosa significa quando usate "risolvi" 2 volte sulla stessa linea? Tipo: attendi nuova promessa (resol => setTimeout (resolve, 1000)); fa rif. a se stesso o cosa? Vorrei fare qualcosa del genere invece: function myFunc () {}; attendi nuova promessa (resol => setTimeout (myFunc, 1000));
PabloDK,

35

setTimeoutnon è una asyncfunzione, quindi non è possibile utilizzarlo con ES7 async-waitit. Ma potresti implementare la tua sleepfunzione usando ES6 Promise :

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

Quindi sarai in grado di utilizzare questa nuova sleepfunzione con ES7 async-await:

var fileList = await sleep(listFiles, nextPageToken)

Per favore, nota che sto solo rispondendo alla tua domanda sulla combinazione di ES7 asincrono / wait setTimeout, anche se potrebbe non aiutare a risolvere il tuo problema con l'invio di troppe richieste al secondo.


Aggiornamento: le moderne versioni di node.js hanno un'implementazione di timeout asincrono integrata , accessibile tramite l' utilità util.promisify :

const {promisify} = require('util');
const setTimeoutAsync = promisify(setTimeout);

2
Non dovresti farlo, quando i fntiri l'errore non verrebbero colti.
Bergi,

@Bergi Penso che bolle fino a new Promisedove puoi sleep.catchfarlo.
Florian Wendelborn,

3
@Dodekeract No, è in un setTimeoutcallback asincrono e il new Promisecallback è stato fatto per molto tempo. Passerà al contesto globale e verrà lanciato come un'eccezione non gestita.
Bergi,

> problema con l'invio di troppe richieste al secondo. Volete usare "debounce" forse per evitare che cose come l'interfaccia utente sparino troppe ruquest.
FlavorScape

5

Se desideri utilizzare lo stesso tipo di sintassi, setTimeoutpuoi scrivere una funzione di supporto come questa:

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

Puoi quindi chiamarlo così:

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

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

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

doStuffAsync();

Ho fatto un'idea: https://gist.github.com/DaveBitter/f44889a2a52ad16b6a5129c39444bb57


1
un nome di funzione come delayRunavrebbe più senso qui, poiché ritarderebbe l'esecuzione della funzione di richiamata di X secondi. Non un esempio molto atteso, IMO.
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

0

Il seguente codice funziona in Chrome e Firefox e forse in altri browser.

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

Ma in Internet Explorer ricevo un errore di sintassi per il "(resolve **=>** setTimeout..."


0

Fatto un util ispirato da Dave 's risposta

Fondamentalmente passato in un donecallback per chiamare al termine dell'operazione.

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

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

0

Questa è la mia versione con nodejs ora nel 2020 in labdas AWS

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

-3

Questa è una soluzione più rapida in una riga.

Spero che questo possa aiutare.

// WAIT FOR 200 MILISECONDS TO GET DATA //
await setTimeout(()=>{}, 200);

1
Non funziona Questo: await setTimeout(()=>{console.log('first')}, 200); console.log ('second')stampa seconda poi prima
gregn3

1
@ gregn3 questo è il punto sì. Questa è una soluzione non bloccante in cui il codice esterno alla funzione può continuare a essere eseguito mentre "un'operazione di blocco" viene completata al di fuori del flusso del programma principale. Sebbene la sintassi fornita da te, Rommy e Mohamad non sia strettamente corretta a causa del requisito per l'attesa di essere rappato in una funzione asincrona (potrebbe essere un'aggiunta abbastanza recente), sto anche usando node.js. Questa è la mia soluzione ottimizzata. var test = async () => { await setTimeout(()=>{console.log('first')}, 1000); console.log ('second') }Ho esteso il timeout per mostrare la sua utilità.
Azariah,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.