25

How can I add a setTimeout to my async await function call?

I have

    request = await getProduct(productids[i]);

where

const getProduct = async productid => {
        return requestPromise(url + productid);
   };

I've tried

    request = await setTimeout((getProduct(productids[i])), 5000);

and got the error TypeError: "callback" argument must be a function which makes sense. The request is inside of a loop which is making me hit the rate limit on an api call.

exports.getProducts = async (req, res) => {
  let request;
  for (let i = 0; i <= productids.length - 1; i++) {
    request = await getProduct(productids[i]);
    //I want to wait 5 seconds before making another call in this loop!
  }
};
jenryb
  • 2,017
  • 12
  • 35
  • 72
  • Here's a pretty similar question: [How to make nightmare request timeout](https://stackoverflow.com/questions/50033704/how-to-make-nightmare-forcefully-timeout/50035402#50035402). – jfriend00 Apr 29 '18 at 23:31
  • Do you mean that the request executes 1 second from now or you start it immediately and if it doesn't finish within 1 second, then you time it out? – jfriend00 Apr 29 '18 at 23:32
  • FYI, the request-promise library supports a timeout option already so if you just want to timeout waiting for a response, you can use the option already built into the [request-promise](https://www.npmjs.com/package/request) library. – jfriend00 Apr 29 '18 at 23:35
  • @jfriend00 thanks for the comments. I actually want to put a wait in the function so that after one request it will wait before making another request. I didn't see the timeout option in the library before, thanks for linking that. – jenryb Apr 30 '18 at 00:18
  • That timeout option is not a wait option for the next request. Those are completely different things. – jfriend00 Apr 30 '18 at 00:21
  • @jfriend00 I understand that. I added some details to the question to hopefully clear up that I don't need a timeout for the API call but a break between calls – jenryb Apr 30 '18 at 00:22

5 Answers5

39

You can use a simple little function that returns a promise that resolves after a delay:

function delay(t, val) {
   return new Promise(function(resolve) {
       setTimeout(function() {
           resolve(val);
       }, t);
   });
}

// or a more condensed version
const delay = (t, val) => new Promise(resolve => setTimeout(resolve, t, val));

And, then await that inside your loop:

exports.getProducts = async (req, res) => {
  let request;
  for (let id of productids) {
    request = await getProduct(id);
    await delay(5000);
  }
};

Note: I also switched your for loop to use for/of which is not required, but is a bit cleaner than what you had.


Or, in modern versions of nodejs, you can use timersPromises.setTimeout() which is a built-in timer that returns a promise (as of nodejs v15):

const setTimeoutP = require('timers/promises').setTimeout;

exports.getProducts = async (req, res) => {
  let request;
  for (let id of productids) {
    request = await getProduct(id);
    await setTimeoutP(5000);
  }
};
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • 1
    Thank you! This is perfect, thanks for including the example usage. Also appreciate the change of the for loop, didn't know you could do that, so I learned double from this answer. – jenryb Apr 30 '18 at 00:26
  • One question I have, do you know if the async method using await is waiting for one api call to finish before making the next? If it is, then this delay is necessary, if it's not, I might need to find a way to make series calls to avoid hardcoding in a delay like this. – jenryb Apr 30 '18 at 00:35
  • 2
    @jenryb - If `getProduct(id)` returns a promise that is only resolved when its asynchronous operation is done, then `await getProduct(id)` will indeed wait for it to finish and the `for` loop will indeed wait for each call to `getProduct(id)`. But (and this is important), your exported function `getProducts()` does not wait to return. It returns immediately and returns a promise that is resolved when the `for` loop and all the `await` operations are all done. – jfriend00 Apr 30 '18 at 00:59
13

Actually, I have a pretty standard chunk of code that I use to do that:

function PromiseTimeout(delayms) {
    return new Promise(function (resolve, reject) {
        setTimeout(resolve, delayms);
    });
}

Usage:

await PromiseTimeout(1000);

If you're using Bluebird promises, then it's built in as Promise.timeout.

More to your problem: Have you checked API docs? Some APIs tell you how much you have to wait before next request. Or allow downloading data in larger bulk.

Giulio Bambini
  • 4,695
  • 4
  • 21
  • 36
  • 1
    In Bluebird, it's [Promise.delay](http://bluebirdjs.com/docs/api/promise.delay.html) instead of Promise.timeout. – Blade1336 Jul 27 '20 at 00:46
11

As of node v15 you can use the Timers Promises API:

const timersPromises = require('timers/promises');

async function test() {
  await timersPromises.setTimeout(1000);
}

test();

Note that this feature is experimental and may change in future versions.

Leifb
  • 370
  • 7
  • 20
3

Since Node 15 and above, there is the new Timers Promises API that let you to avoid to build the wrapping:

import {
  setTimeout,
  setImmediate,
  setInterval,
} from 'timers/promises';

console.log('before')
await setTimeout(1000)
console.log('after 1 sec')

So your issues you could write it with async iterator:

import {
  setTimeout
} from 'timers/promises'

async function getProducts (req, res) {
  const productids = [1, 2, 3]

  for await (const product of processData(productids)) {
    console.log(product)
  }
}

async function * processData (productids) {
  while (productids.length > 0) {
    const id = productids.pop()
    const product = { id }
    yield product
    await setTimeout(5000)
  }
}

getProducts()

Manuel Spigolon
  • 11,003
  • 5
  • 50
  • 73
-1

I have done api delay test as below. It is possible to delay it as if by hanging setTimeout.

  sleep(ms) {
    const wakeUpTime = Date.now() + ms;
    while (Date.now() < wakeUpTime) {}
  }
callAPI = async() => {
    ...  // Execute api logic 
    await this.sleep(2147483647);
    ...  // Execute api logic 
}
await callAPI();
HellJosun
  • 129
  • 1
  • 10