Kombination aus asynchroner Funktion + Warten + SetTimeout

306

Ich versuche, die neuen asynchronen Funktionen zu verwenden, und hoffe, dass die Lösung meines Problems in Zukunft anderen helfen wird. Dies ist mein Code, der funktioniert:

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

Das Problem ist, dass meine while-Schleife zu schnell ausgeführt wird und das Skript zu viele Anforderungen pro Sekunde an die Google-API sendet. Daher möchte ich eine Sleep-Funktion erstellen, die die Anfrage verzögert. Somit könnte ich diese Funktion auch verwenden, um andere Anfragen zu verzögern. Wenn es eine andere Möglichkeit gibt, die Anfrage zu verzögern, lassen Sie es mich bitte wissen.

Auf jeden Fall ist dies mein neuer Code, der nicht funktioniert. Die Antwort der Anfrage wird an die anonyme asynchrone Funktion innerhalb von setTimeout zurückgegeben, aber ich weiß einfach nicht, wie ich die Antwort an die Sleep-Funktion bzw. die Sleep-Funktion zurückgeben kann. zur anfänglichen asyncGenerator-Funktion.

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

Ich habe bereits einige Optionen ausprobiert: Speichern der Antwort in einer globalen Variablen und Zurückgeben von der Sleep-Funktion, Rückruf innerhalb der anonymen Funktion usw.

JShinigami
quelle

Antworten:

615

Ihre sleepFunktion funktioniert nicht, weil setTimeout(noch?) Kein Versprechen zurückgegeben wird, das bearbeitet werden könnte await. Sie müssen es manuell versprechen:

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

Übrigens, um Ihre Schleife zu verlangsamen, möchten Sie wahrscheinlich keine sleepFunktion verwenden, die einen Rückruf entgegennimmt und ihn so verzögert. Ich würde eher empfehlen, so etwas zu tun

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

Dadurch kann die Berechnung von parentsmindestens 5 Sekunden dauern.

Bergi
quelle
11
Ich liebe den Promise.allAnsatz. So einfach und elegant!
Anshul Koka
4
Was bedeutet die Notation von var [parents]? Ich habe es noch nie gesehen und es ist eine schwierige Sache zu googeln
natedog
6
@NateUsher Es ist Array Destrukturierung
Bergi
1
@tinkerr " Timeout muss als asynchron deklariert werden, wenn darauf gewartet werden muss " - Nein. Eine Funktion muss nur ein Versprechen zurückgeben, das erwartet werden kann (oder tatsächlich reicht ein dannable). Wie dies erreicht wird, hängt von der Implementierung der Funktion ab. Es muss keine sein async function.
Bergi
2
@naisanza Nein, async/ awaitwird auf Basis verspricht. Das einzige, was es ersetzt, sind thenAnrufe.
Bergi
152

Seit Knoten 7.6 können Sie die promisifyFunktionsfunktion aus dem Utils-Modul mit kombinieren setTimeout().

Node.js

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

Javascript

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

Verwendung

(async () => {
    console.time("Slept for")
    await sleep(3000)
    console.timeEnd("Slept for")
})()
Harry
quelle
1
In NodeJS await require('util').promisify(setTimeout)(3000)kann auch ohne Bedarf erreicht werden durch:await setTimeout[Object.getOwnPropertySymbols(setTimeout)[0]](3000)
Shl
5
Interessante @Shl. Ich denke, es ist weniger lesbar als meine Lösung. Wenn die Leute nicht einverstanden sind, kann ich es der Lösung hinzufügen?
Harry
2
Die erforderliche Version ist eindeutig viel besser als die getOwnPropertySymbolsVersion ... wenn sie nicht kaputt ist ...!
Matt Fletcher
2
Hey da @Harry. Anscheinend haben Sie den einen Liner aus der Antwort von FlavorScape in Ihre eigene Antwort aufgenommen. Ich möchte nicht von Ihren Absichten ausgehen, aber das ist ihnen gegenüber nicht wirklich fair. Könnten Sie Ihre Bearbeitung rückgängig machen? Im Moment sieht es ein bisschen nach Plagiat aus.
Félix Gagnon-Grenier
2
Ich habe den Einzeiler entfernt, da die Antwort direkt unten steht. Ich habe jedoch viele beliebte Antworten gesehen, die ihre Antworten aktualisiert haben, um andere neue Antworten aufzunehmen, da sich die meisten Leser nicht die Mühe machen, über die ersten Antworten hinauszuschauen.
Harry
130

Der schnelle Einzeiler inline

 await new Promise(resolve => setTimeout(resolve, 1000));
FlavourScape
quelle
4
let sleep = ms => new Promise( r => setTimeout(r, ms));// eine
Einzeilerfunktion
8
noch kürzer :-)await new Promise(resolve => setTimeout(resolve, 5000))
Liran Brimer
1
Was bedeutet es, wenn ihr "Auflösung" x 2 Mal in derselben Zeile verwendet? Gefällt mir: warte auf ein neues Versprechen (Auflösung => setTimeout (Auflösung, 1000)); tut es ref. zu sich selbst oder was? Ich würde stattdessen so etwas tun: function myFunc () {}; auf neues Versprechen warten (resolve => setTimeout (myFunc, 1000));
PabloDK
35

setTimeoutist keine asyncFunktion, daher können Sie sie nicht mit ES7 async-await verwenden. Sie können Ihre sleepFunktion jedoch mit ES6 Promise implementieren :

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

Dann können Sie diese neue sleepFunktion mit ES7 async-await verwenden:

var fileList = await sleep(listFiles, nextPageToken)

Bitte beachten Sie, dass ich nur Ihre Frage zum Kombinieren von ES7 async / await mit beantworte setTimeout, obwohl dies möglicherweise nicht zur Lösung Ihres Problems beim Senden zu vieler Anfragen pro Sekunde beiträgt .


Update: Moderne node.js-Versionen verfügen über eine integrierte asynchrone Timeout-Implementierung, auf die über den Hilfsprogramm util.promisify zugegriffen werden kann :

const {promisify} = require('util');
const setTimeoutAsync = promisify(setTimeout);
Leonid Beschastny
quelle
2
Sie sollten das nicht tun, wenn fnWürfe den Fehler nicht abfangen würden.
Bergi
@Bergi Ich denke, es sprudelt bis dahin, new Promisewo du es kannst sleep.catch.
Florian Wendelborn
3
@Dodekeract Nein, es handelt sich um einen asynchronen setTimeoutRückruf, und der new PromiseRückruf wurde lange durchgeführt. Es wird in den globalen Kontext sprudeln und als unbehandelte Ausnahme ausgelöst.
Bergi
> Problem beim Senden zu vieler Anfragen pro Sekunde. Sie möchten "entprellen" verwenden, um zu verhindern, dass Dinge wie die Benutzeroberfläche zu viele Ruquests auslösen.
FlavorScape
5

Wenn Sie dieselbe Syntax verwenden möchten, wie setTimeoutSie eine Hilfsfunktion wie diese schreiben können:

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

Sie können es dann so nennen:

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

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

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

doStuffAsync();

Ich habe eine Zusammenfassung erstellt: https://gist.github.com/DaveBitter/f44889a2a52ad16b6a5129c39444bb57

Dave Bitter
quelle
1
Ein Funktionsname wie delayRunwäre hier sinnvoller, da er die Ausführung der Rückruffunktion um X Sekunden verzögert. Kein sehr erwartetes Beispiel, 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
Vignesh
quelle
0

Der folgende Code funktioniert in Chrome und Firefox und möglicherweise in anderen Browsern.

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

Aber in Internet Explorer bekomme ich einen Syntaxfehler für die "(resolve **=>** setTimeout..."

Schatten
quelle
0

Aus Dave 's Antwort inspiriert

Grundsätzlich wird ein doneRückruf übergeben, um nach Abschluss des Vorgangs aufzurufen.

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

So benutze ich es:

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
quelle
0

Dies ist meine Version mit NodeJS jetzt im Jahr 2020 in 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
quelle
-3

Dies ist eine schnellere Lösung für Einzeiler.

Hoffe das wird helfen.

// WAIT FOR 200 MILISECONDS TO GET DATA //
await setTimeout(()=>{}, 200);
Rommy Garg
quelle
1
Funktioniert nicht Diese: await setTimeout(()=>{console.log('first')}, 200); console.log ('second')druckt zweite dann zuerst
gregn3
1
@ Gregn3 das ist der Punkt ja. Dies ist eine nicht blockierende Lösung, bei der Code außerhalb der Funktion weiterhin ausgeführt werden kann, während eine "Blockierungsoperation" außerhalb des Hauptprogrammflusses abgeschlossen wird. Obwohl die von Ihnen, Rommy und Mohamad angegebene Syntax nicht unbedingt korrekt ist, da in einer asynchronen Funktion eine Wartezeit abgehört werden muss (möglicherweise eine relativ neue Ergänzung), verwende ich auch node.js. Dies ist meine optimierte Lösung. var test = async () => { await setTimeout(()=>{console.log('first')}, 1000); console.log ('second') }Ich habe das Timeout verlängert, um seine Nützlichkeit zu demonstrieren.
Azariah