Połączenie funkcji asynchronicznej + oczekiwanie + setTimeout

305

Próbuję korzystać z nowych funkcji asynchronicznych i mam nadzieję, że rozwiązanie mojego problemu pomoże innym w przyszłości. To jest mój kod, który działa:

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

Problem polega na tym, że moja pętla while działa zbyt szybko, a skrypt wysyła zbyt wiele żądań na sekundę do interfejsu API Google. Dlatego chciałbym zbudować funkcję uśpienia, która opóźnia żądanie. W ten sposób mógłbym również użyć tej funkcji do opóźnienia innych żądań. Jeśli istnieje inny sposób opóźnienia żądania, daj mi znać.

W każdym razie to mój nowy kod, który nie działa. Odpowiedź żądania jest zwracana do anonimowej funkcji asynchronicznej w ramach setTimeout, ale po prostu nie wiem, jak mogę zwrócić odpowiedź na funkcję uśpienia lub. do początkowej funkcji 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);
  }

Próbowałem już kilka opcji: przechowywanie odpowiedzi w zmiennej globalnej i zwrócenie jej z funkcji uśpienia, wywołanie zwrotne w funkcji anonimowej itp.

JShinigami
źródło

Odpowiedzi:

614

Twoja sleepfunkcja nie działa, ponieważ setTimeout(jeszcze?) Nie zwraca obietnicy, którą można by awaitedytować. Musisz ręcznie obiecać:

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

Przy okazji, aby spowolnić pętlę, prawdopodobnie nie chcesz używać sleepfunkcji, która odbiera wywołanie zwrotne i odkłada go w ten sposób. Wolałbym raczej zrobić coś takiego

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

co pozwala na obliczenie parentsco najmniej 5 sekund.

Bergi
źródło
11
Uwielbiam Promise.allpodejście. Tak prosty i elegancki!
Anshul Koka
4
co oznacza notacja var [parents]? Nie widziałem tego wcześniej i trudno google google
natedog
6
@NateUsher To jest restrukturyzacja macierzy
Bergi
1
@tinkerr limit czasu musi zostać zadeklarowany jako asynchroniczny, jeśli trzeba go czekać - Nie. Funkcja musi tylko zwrócić obietnicę, na którą można się poczekać (lub faktycznie, to nie wystarcza). Jak to osiąga, zależy od implementacji funkcji, nie musi być async function.
Bergi
2
@naisanza Nie, async/ awaitjest na podstawie obietnic. Jedyne, co zastępuje, to thenpołączenia.
Bergi,
150

Od Węzła 7.6 możesz łączyć funkcje promisifyfunkcji z modułu utils z setTimeout().

Node.js

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

JavaScript

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

Stosowanie

(async () => {
    console.time("Slept for")
    await sleep(3000)
    console.timeEnd("Slept for")
})()
Złupić
źródło
1
W węźle JS await require('util').promisify(setTimeout)(3000)można również osiągnąć bez konieczności:await setTimeout[Object.getOwnPropertySymbols(setTimeout)[0]](3000)
Shl 24.08.18
5
Ciekawe @Shl. Myślę jednak, że jest mniej czytelny niż moje rozwiązanie. Jeśli ludzie się nie zgadzają, mogę dodać to do rozwiązania?
Harry
2
Wymagana wersja jest wyraźnie lepsza niż getOwnPropertySymbolswersja ... jeśli nie jest zepsuta ...!
Matt Fletcher,
2
Hej tam @Harry. Wygląda na to, że umieściłeś jedną linijkę z odpowiedzi FlavorScape we własnej odpowiedzi. Nie chcę zakładać o twoich zamiarach, ale to nie jest dla nich uczciwe. Czy możesz cofnąć swoją edycję? W tej chwili wygląda to trochę jak plagiat.
Félix Gagnon-Grenier
2
Usunąłem linijkę, ponieważ odpowiedź jest tuż poniżej, ale widziałem, jak wiele popularnych odpowiedzi aktualizuje swoje odpowiedzi, aby zawierały inne nowe odpowiedzi, ponieważ większość czytelników nie zawraca sobie głowy kilkoma pierwszymi odpowiedziami.
Harry,
129

Szybki, liniowy, liniowy sposób

 await new Promise(resolve => setTimeout(resolve, 1000));
FlavorScape
źródło
3
let sleep = ms => new Promise( r => setTimeout(r, ms));// funkcja jednego
linijki
8
jeszcze krótszy :-)await new Promise(resolve => setTimeout(resolve, 5000))
Liran Brimer
1
co to znaczy, kiedy używacie „rozwiązać” x 2 razy w tej samej linii? Jak: czekaj na nową obietnicę (rozdzielczość => setTimeout (rozdzielczość, 1000)); czy to ref. do siebie czy co? Zamiast tego zrobiłbym coś takiego: function myFunc () {}; czekaj na nową obietnicę (rozdzielczość => setTimeout (myFunc, 1000));
PabloDK
35

setTimeoutnie jest asyncfunkcją, więc nie można jej używać z asynchronicznym oczekiwaniem na ES7. Ale możesz zaimplementować swoją sleepfunkcję za pomocą ES6 Promise :

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

Następnie będziesz mógł użyć tej nowej sleepfunkcji z asynchronicznym oczekiwaniem na ES7:

var fileList = await sleep(listFiles, nextPageToken)

Pamiętaj , że odpowiadam tylko na twoje pytanie dotyczące połączenia asynchronicznego / oczekującego ES7 z setTimeout, choć może nie pomóc rozwiązać problemu z wysyłaniem zbyt wielu żądań na sekundę.


Aktualizacja: Nowoczesne wersje node.js mają wbudowaną implementację limitu czasu asynchronicznego dostępu, dostępną za pośrednictwem pomocnika util.promisify :

const {promisify} = require('util');
const setTimeoutAsync = promisify(setTimeout);
Leonid Beschastny
źródło
2
Nie powinieneś tego robić, gdy fnrzuty nie zostaną złapane.
Bergi,
@Bergi Wydaje mi się, że bańka sięga new Promisetam, gdzie możesz sleep.catch.
Florian Wendelborn,
3
@Dodekeract Nie, jest w asynchronicznym setTimeoutwywołaniu zwrotnym, a new Promisewywołanie zwrotne zostało wykonane przez długi czas. Zostanie przeniesiony do kontekstu globalnego i zostanie zgłoszony jako nieobsługiwany wyjątek.
Bergi,
> problem z wysyłaniem zbyt wielu żądań na sekundę. Chcesz użyć „debounce”, być może, aby uniemożliwić uruchamianie zbyt wielu ruquestów przez interfejs użytkownika.
FlavorScape,
5

Jeśli chcesz użyć tego samego rodzaju składni, jak setTimeoutmożesz napisać funkcję pomocniczą w następujący sposób:

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

Możesz to tak nazwać:

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

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

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

doStuffAsync();

Zrobiłem sedno: https://gist.github.com/DaveBitter/f44889a2a52ad16b6a5129c39444bb57

Dave Bitter
źródło
1
delayRunbardziej sensowna byłaby tutaj nazwa funkcji , ponieważ opóźni ona uruchomienie funkcji zwrotnej o X sekund. Niezbyt oczekiwany przykład, 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
winiet
źródło
0

Poniższy kod działa w Chrome i Firefox i być może w innych przeglądarkach.

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

Ale w Internet Explorerze pojawia się błąd składni dla "(resolve **=>** setTimeout..."

Zacieniowane
źródło
0

Popełnił util inspirowane z Dave „s odpowiedź

Zasadniczo przekazano w donewywołaniu zwrotnym, aby zadzwonić po zakończeniu operacji.

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

Tak to wykorzystuję:

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
źródło
0

To jest moja wersja z nodejs teraz w 2020 roku w 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)
    ...
}
zwitterion
źródło
-3

Jest to szybsze rozwiązanie w jednej linijce.

Mam nadzieję, że to pomoże.

// WAIT FOR 200 MILISECONDS TO GET DATA //
await setTimeout(()=>{}, 200);
Rommy Garg
źródło
1
Nie działa To: await setTimeout(()=>{console.log('first')}, 200); console.log ('second')drukuje drugi, a potem pierwszy
gregn3
1
@ gregn3 o to właśnie chodzi. Jest to rozwiązanie nieblokujące, w którym kod poza funkcją może być nadal wykonywany, podczas gdy „operacja blokowania” jest wykonywana poza głównym programem. Chociaż składnia, którą Ty, Rommy i Mohamad podaliście, nie jest ściśle poprawna z powodu wymogu oczekiwania na rapowanie w funkcji asynchronicznej (może to być dość nowy dodatek), również używam node.js. To jest moje poprawione rozwiązanie. var test = async () => { await setTimeout(()=>{console.log('first')}, 1000); console.log ('second') }Wydłużyłem limit czasu, aby pokazać jego przydatność.
azariah