Combinação da função assíncrona + aguardar + setTimeout


307

Estou tentando usar os novos recursos assíncronos e espero que resolver meu problema ajude outras pessoas no futuro. Este é o meu 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
    });
  }

O problema é que meu loop while é executado muito rápido e o script envia muitas solicitações por segundo para a API do Google. Portanto, eu gostaria de criar uma função de suspensão que atrasa a solicitação. Assim, eu também poderia usar essa função para atrasar outros pedidos. Se houver outra maneira de atrasar a solicitação, entre em contato.

De qualquer forma, este é o meu novo código que não funciona. A resposta da solicitação é retornada à função assíncrona anônima dentro do setTimeout, mas simplesmente não sei como posso retornar a resposta à função de suspensão resp. para a função asyncGenerator inicial.

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

Eu já tentei algumas opções: armazenar a resposta em uma variável global e retorná-la da função de suspensão, retorno de chamada na função anônima etc.

Respostas:


615

Sua sleepfunção não funciona porque setTimeout(ainda?) Não retorna uma promessa que poderia ser cumprida await. Você precisará promisificá-lo manualmente:

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

Btw, para desacelerar seu loop, você provavelmente não deseja usar uma sleepfunção que recebe um retorno de chamada e o adia dessa maneira. Prefiro recomendar algo como

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

o que permite que o cálculo parentsdemore pelo menos 5 segundos.


11
Adoro a Promise.allabordagem. Tão simples e elegante!
Anshul Koka

4
o que a notação de var [parents]representa? Eu não vi isso antes e é uma coisa difícil de google
natedog

6
De @NateUsher Ele desestruturação variedade
Bergi

1
@tinkerr "o tempo limite precisa ser declarado assíncrono se precisar ser aguardado " - Não. Uma função precisa apenas retornar uma promessa que pode ser aguardada (ou, na verdade, uma tabela é suficiente). Como ele consegue atingir a implementação da função, ele não precisa ser um async function.
Bergi 23/11

2
@naisanza Não, async/ awaité baseado em promessas. A única coisa que substitui são as thenchamadas.
Bergi 13/03/19

152

Desde o nó 7.6 , você pode combinar a função de promisifyfunções do módulo utils com 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
No nodeJS await require('util').promisify(setTimeout)(3000)também pode ser alcançado sem a necessidade de:await setTimeout[Object.getOwnPropertySymbols(setTimeout)[0]](3000)
Shl

5
Interessante @Shl. Eu acho que é menos legível do que minha solução. Se as pessoas discordarem, posso adicioná-lo à solução?
Harry

2
A versão requerida é claramente muito melhor que a getOwnPropertySymbolsversão ... se não estiver quebrada ...!
Matt Fletcher

2
Olá, Harry. Parece que você incorporou o liner da resposta do FlavorScape em sua própria resposta. Não quero presumir suas intenções, mas isso não é realmente justo para elas. Você poderia reverter sua edição? Neste momento, parece um pouco com plágio ..
Félix Gagnon-Grenier

2
Eu removi o one-liner porque a resposta está logo abaixo, no entanto, tenho visto muitas respostas populares atualizando suas respostas para incluir outras novas respostas, pois a maioria dos leitores não se incomoda em olhar além das primeiras respostas.
Harry

130

A maneira rápida, de uma linha e em linha

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

4
let sleep = ms => new Promise( r => setTimeout(r, ms));// uma função de um forro
Soldeplata Saketos

8
ainda mais curto :-)await new Promise(resolve => setTimeout(resolve, 5000))
Liran Brimer

1
o que significa quando vocês usam "resolve" x 2 vezes na mesma linha? Como: aguardar nova promessa (resolve => setTimeout (resolve, 1000)); faz ref. para si ou o quê? Eu faria algo assim: function myFunc () {}; aguardar nova promessa (resolve => setTimeout (myFunc, 1000));
PabloDK

35

setTimeoutnão é uma asyncfunção, portanto você não pode usá-lo com o ES7 async-waitit. Mas você pode implementar sua sleepfunção usando o ES6 Promise :

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

Então você poderá usar esta nova sleepfunção com o ES7 async-waitit:

var fileList = await sleep(listFiles, nextPageToken)

Observe que estou respondendo apenas à sua pergunta sobre a combinação de ES7 assíncrono / aguardar setTimeout, embora possa não ajudar a resolver seu problema com o envio de muitas solicitações por segundo.


Atualização: As versões modernas do node.js. possuem uma implementação de tempo limite assíncrono, acessível por meio do utilitário util.promisify :

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

2
Você não deve fazer isso, quando fnlança o erro não seria pego.
Bergi 23/10/2015

@ Bergi Eu acho que borbulha até new Promiseonde você pode sleep.catch.
Florian Wendelborn

3
@Dodekeract Não, está em um setTimeoutretorno de chamada assíncrono e o new Promiseretorno de chamada foi feito por muito tempo. Irá borbulhar para o contexto global e será lançada como uma exceção não tratada.
Bergi 5/03

> problema com o envio de muitos pedidos por segundo. Você deseja usar "debounce" talvez para impedir que coisas como a interface do usuário disparem muitas ruquests.
FlavorScape 5/08/19

5

Se você deseja usar o mesmo tipo de sintaxe, setTimeoutpode escrever uma função auxiliar como esta:

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

Você pode chamá-lo assim:

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

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

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

doStuffAsync();

Eu fiz uma essência: https://gist.github.com/DaveBitter/f44889a2a52ad16b6a5129c39444bb57


1
um nome de função como delayRunfaria mais sentido aqui, pois atrasará a execução da função de retorno de chamada em X segundos. Não é um exemplo muito aguardado, 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

O código a seguir funciona no Chrome e Firefox e talvez em outros navegadores.

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

Mas no Internet Explorer, recebo um erro de sintaxe para o "(resolve **=>** setTimeout..."


0

Fez uma util inspirada de Dave 's resposta

Passado basicamente em um doneretorno de chamada para chamar quando a operação estiver concluída.

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

É assim que eu 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

Esta é a minha versão com o nodejs agora em 2020 nas 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)
    ...
}

-3

Esta é uma correção mais rápida em uma linha.

Espero que isso ajude.

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

1
Não funciona Isto: await setTimeout(()=>{console.log('first')}, 200); console.log ('second')impressões segunda seguida primeiros
gregn3

1
@ Gregn3 esse é o ponto sim. Esta é uma solução sem bloqueio, na qual o código fora da função pode continuar sendo executado enquanto uma "operação de bloqueio" é concluída fora do fluxo principal do programa. Embora a sintaxe que você e Rommy e Mohamad tenham fornecido não esteja estritamente correta devido ao requisito de que uma espera seja acelerada em uma função assíncrona (pode ser uma adição relativamente recente), também estou usando o node.js. Esta é a minha solução aprimorada. var test = async () => { await setTimeout(()=>{console.log('first')}, 1000); console.log ('second') }Estendi o tempo limite para mostrar sua utilidade.
azariah 9/11/19
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.