JShinigami
JShinigami

Reputation: 8005

Combination of async function + await + setTimeout

I am trying to use the new async features and I hope solving my problem will help others in the future. This is my code which is working:

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

The problem is, that my while loop runs too fast and the script sends too many requests per second to the google API. Therefore I would like to build a sleep function which delays the request. Thus I could also use this function to delay other requests. If there is another way to delay the request, please let me know.

Anyway, this is my new code which does not work. The response of the request is returned to the anonymous async function within the setTimeout, but I just do not know how I can return the response to the sleep function resp. to the initial asyncGenerator function.

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

I have already tried some options: storing the response in a global variable and return it from the sleep function, callback within the anonymous function, etc.

Upvotes: 746

Views: 959877

Answers (19)

brk.gs
brk.gs

Reputation: 15

setTimeout(async () => {
   var x = await asyncFunction(); 
}, 5000)

Upvotes: -2

Jiří
Jiří

Reputation: 525

I would like to point out a robust extension to Promise.all. A rather elegant solution that works with one promise to be time-limited only is to race the promise with a timeout (such as new Promise((resolve) => setTimeout(resolve, timeout))).

await new Promise.race([myPromise, timeoutPromise])

will continue as soon as one of the promises finished. myPromise then can internally await a different timeout, or simply make use of Promise.all

const timeout = ms => new Promise((resolve) => setTimeout(resolve, ms));
await Promise.race([
    Promise.all([myPromise, timeout(500)]),
    timeout(5000)
]);

The result is an asynchronous call that does not run more often than twice a second, with a timeout of 5 seconds in case of some (network/server?) error.

Moreover, you can make this very versatile and customizable function as such:

function callWithTimeout(promise, msTimeout=5000, throws=false) {
    const timeout = ms => new Promise((resolve, reject) =>
        setTimeout(throws ? reject : resolve, ms));
    await Promise.race([
        //depends whether you want to wait there or just pass the promise itself
        Promise.all([promise, timeout(500)]), 
        timeout(msTimeout)
    ]);
}
    

Which ultimately lets you customize the timeout time and the whether the promise should succeed or throw on timeout. Having such robust general implementation can save you a lot of pain in the future. You can also set a string instead of boolean as throws and bind this variable to the reject for custom error message: reject.bind(undefined, throws)

Note that you should not pass your promise with await:

const myPromise = async x => x;
//will never time out and not because myPromise will finish immediatelly
callWithTimeout(await myPromise(), 2000, true); 
//will possibly timeout after 2 sec with an exception, or finish 
callWithTimeout(myPromise(), 2000, true); 

Upvotes: 4

royce3
royce3

Reputation: 1422

I wanted to solve what I think is a related problem: the ability to auto-refresh a page every 30 seconds, but also live update some timers on-screen once a second and I wanted to make sure these 2 things didn't conflict. My google searches led me to your question here, so I thought I would share what I came up with in case it helps others:

class Elapsed
{
    constructor()
    {
        this.start = performance.now()
    }
    elapsed_seconds( seconds )
    {
        let elapsed_seconds = ( performance.now() - this.start ) * 0.001
        return elapsed_seconds >= seconds
    }
}

async function sleep( seconds )
{
    let promise = new Promise(( resolve, _ ) => {
        setTimeout( () => resolve(), seconds * 1000 )
    })
    await promise
}

async function refresh()
{
    // do something
    return new Elapsed()
}

async function auto_refresh()
{
    while( true )
    {
        let last_refresh = await refresh()
        while( !last_refresh.elapsed_seconds( 30 ))
        {
            await update_counters()
            await sleep( 1 )
        }
    }
}

Upvotes: 0

Kbre
Kbre

Reputation: 83

With the marked answer I had a lint error [no-promise-executor-return] so I found here the corrected version, using curly brackets in order to make explicit the intention of not returning anything:

const timeout = (ms) =>
  new Promise(resolve => {
    setTimeout(resolve, ms)
  })

Upvotes: 4

t_dom93
t_dom93

Reputation: 11456

Timers Promises API

await setTimeout finally arrived with Node.js 16, removing the need to use util.promisify():

import { setTimeout } from 'timers/promises';

(async () => {
  const result = await setTimeout(2000, 'resolved')
  // Executed after 2 seconds
  console.log(result); // "resolved"
})()

Official Node.js docs: Timers Promises API (library already built in Node)

Upvotes: 73

Erik P_yan
Erik P_yan

Reputation: 788

I leave this code snippet here for someone who wants to fetch API call (e.g. get clients) with setTimeout:

const { data } = await new Promise(resolve => setTimeout(resolve, 250)).then(() => getClientsService())
setName(data.name || '')
setEmail(data.email || '')

Upvotes: 17

sounish nath
sounish nath

Reputation: 681

How to Log all the responses at once?

async function sayHello(name) {
  let greet = `Hey! ${name} very nice to meet you bud.`;
  setTimeout(() => {
    return {
      greet,
      createdAt: new Date(),
    };
  }, 1000);
}

const response1 = async () => await sayHello("sounish");
const response2 = async () => await sayHello("alex");
const response3 = async () => await sayHello("bill");

async function getData() {
  const data1 = await sayHello("sounish");
  const data2 = await sayHello("alex");
  const data3 = await sayHello("bill");
  return { data1, data2, data3 };
}

Promise.all([sayHello("sounish"), sayHello("alex"), sayHello("bill")]).then(
  (allResponses) => {
    console.log({ allResponses });
  }
);

getData().then((allData) => {
  console.log({ allData });
});

Upvotes: -1

Budiman Fajar Firdaus
Budiman Fajar Firdaus

Reputation: 101

await new Promise(resolve => setTimeout(() => { resolve({ data: 'your return data'}) }, 1000))

Upvotes: 10

Bergi
Bergi

Reputation: 664164

Your sleep function does not work because setTimeout does not (yet?) return a promise that could be awaited. You will need to promisify it manually:

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

Btw, to slow down your loop you probably don't want to use a sleep function that takes a callback and defers it like this. I recommend:

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

which lets the computation of parents take at least 5 seconds.

Upvotes: 1230

Leask
Leask

Reputation: 51

await setTimeout(()=>{}, 200);

Will work if your Node version is 15 and above.

Upvotes: 4

IgorAlves
IgorAlves

Reputation: 5540

This is my version with nodejs now in 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)
    ...
}

Upvotes: 5

Harry
Harry

Reputation: 3115

Since Node 7.6, you can combine the functions promisify function from the utils module with 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")
})()

Upvotes: 264

Jee Mok
Jee Mok

Reputation: 6556

Made a util inspired from Dave's answer

Basically passed in a done callback to call when the operation is finished.

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

This is how I use it:

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

Upvotes: 2

vignesh
vignesh

Reputation: 101

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

Upvotes: 8

Rommy Garg
Rommy Garg

Reputation: 1

This is a quicker fix in one-liner.

Hope this will help.

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

Upvotes: -8

Leonid Beschastny
Leonid Beschastny

Reputation: 51440

setTimeout is not an async function, so you can't use it with ES7 async-await. But you could implement your sleep function using ES6 Promise:

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

Then you'll be able to use this new sleep function with ES7 async-await:

var fileList = await sleep(listFiles, nextPageToken)

Please, note that I'm only answering your question about combining ES7 async/await with setTimeout, though it may not help solve your problem with sending too many requests per second.


Update: Modern node.js versions has a buid-in async timeout implementation, accessible via util.promisify helper:

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

Upvotes: 61

Shadowned
Shadowned

Reputation: 21

The following code works in Chrome and Firefox and maybe other browsers.

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

But in Internet Explorer I get a Syntax Error for the "(resolve **=>** setTimeout..."

Upvotes: 1

Dave Bitter
Dave Bitter

Reputation: 181

If you would like to use the same kind of syntax as setTimeout you can write a helper function like this:

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

You can then call it like so:

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

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

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

doStuffAsync();

I made a gist: https://gist.github.com/DaveBitter/f44889a2a52ad16b6a5129c39444bb57

Upvotes: 18

FlavorScape
FlavorScape

Reputation: 14269

The quick one-liner, inline way

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

Upvotes: 633

Related Questions