22

I'm running a setTimeout, recursively, so I only want to call the API one time per minute.

Here is my code:

;(async function ticker (minutes) {
  try {
    const res = await coingecko.global()
    const { market_cap_percentage } = res.data.data
    dominance = { btc: market_cap_percentage.btc, eth: market_cap_percentage.eth }
    console.log({ dominance })
  } catch (ex) {
    console.log(ex.message)
  } finally {
    setTimeout(ticker, minutes * 60 * 1000)
  }
})(1)

The problem is:

  • When I start my server, it calls the API immediately
  • It takes one minute to make a second call (expected behaviour)
  • After the second call, it starts calling the API sequentially, without a timeout
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
André Alçada Padez
  • 10,987
  • 24
  • 67
  • 120
  • Okey I also want to know about this. Wondered =) – halilcakar Jan 24 '21 at 20:01
  • 3
    Am I the only one thinking about `setInterval`? – Rohit Kashyap Jan 24 '21 at 20:06
  • 1
    @RohitKashyap `setInterval` does a different job. It calls a function after `interval` milliseconds, no matter what. Instead, OP probably wanted the timer to start after the request succeeded or got an error – Christian Vincenzo Traina Jan 24 '21 at 20:11
  • 1
    Calling the API one time per minute. So why not call the API once, and use `setInterval` to call the API every one minute. If you need further control over `setInterval`, clear intervals based on your conditions. Closures anyone? And, OP is using the `setTimeout` inside `finally`. What difference would it make exactly? @CristianTraìna – Rohit Kashyap Jan 24 '21 at 20:14
  • 1
    @RohitKashyap let's suppose the promise `coingecko.global()` takes 3 seconds to fulfill, in the OP approach the interval is then 63 seconds, instead with `setInterval` it would be 60 seconds – Christian Vincenzo Traina Jan 24 '21 at 20:21
  • 1
    @CristianTraìna ya and that's what the OP wants right? Calling the API once per minute. Not sure what am I missing here? – Rohit Kashyap Jan 24 '21 at 20:23
  • @CristianTraìna As far as I can tell, the only remaining possibility if the request neither succeeded nor got an error is that the request failed to complete at all (i.e, the promise failed to call either the resolve or reject functions). I can't speak to the OP's intent, but I imagine that'd be a case where they'd want to try the request again a minute later anyway. – James_pic Jan 25 '21 at 11:16

6 Answers6

17

It calls it immediately because that's what your code does. It executes the ticker(1) function call immediately.

When you call ticker from the setTimeout(ticker, ...), you aren't passing the minutes parameter to that function - that's why the setTimeout() doesn't delay properly.

If you don't want it executed immediately, then get rid of the IIFE and just start it with a setTimeout(). And, then when you call ticker() from the setTimeout() callback, be sure to pass the minutes arguments to it, either by passing the third argument to setTimeout() or by making a little callback function for it.

Here's one implementation:

async function ticker(minutes) {
  try {
    const res = await coingecko.global()
    const { market_cap_percentage } = res.data.data
    dominance = { btc: market_cap_percentage.btc, eth: market_cap_percentage.eth }
    console.log({ dominance })
  } catch (ex) {
    console.log(ex.message)
  } finally {
    setTimeout(ticker, minutes * 60 * 1000, minutes);
  }
}

setTimeout(ticker, minutes * 60 * 1000, 1);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
jfriend00
  • 683,504
  • 96
  • 985
  • 979
4

Maybe you just need to use some schedule manager, like bree?

As of your code

  1. you don't need IIFE here. Just call setTimeout(() => ticker(1), minutes * 60 * 1000)
  2. change the inner setTimeout call likewise to pass the minutes parameter, because right now you just pass undefined. That means immediately for setTimeout.
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Anatoly
  • 20,799
  • 3
  • 28
  • 42
3

tickerexpects minutesas argument, so you have to pass the minutes when calling it inside the setTimeout. Besides that, setTimeout expects a function in the first argument, so I suggest to simply pass an arrow function which calls yours tickerfunction. Please check the following code:

;(async function ticker (minutes) {
  try {
    const res = await coingecko.global()
    const { market_cap_percentage } = res.data.data
    dominance = { btc: market_cap_percentage.btc, eth: market_cap_percentage.eth }
    console.log({ dominance })
  } catch (ex) {
    console.log(ex.message)
  } finally {
    setTimeout(() => ticker(minutes), minutes * 60 * 1000)
  }
})(1)
dfvc
  • 404
  • 4
  • 6
1

There are already some good explanations here, but I'm going to refactor the code a bit to take advantage of async/await without needing an external library.

First, let's make setTimeout an async function:

async function sleep(ms) {
  return new Promise(res => setTimeout(res, ms));
}

Next, let's use that instead of setTimeout:

(async function ticker (minutes) {
  do {
    await sleep(minutes * 60 * 1000);
    await loadData();
  } while (true);

  async function loadData() {
    try {
      const res = await coingecko.global()
      const { market_cap_percentage } = res.data.data
      dominance = { btc: market_cap_percentage.btc, eth: market_cap_percentage.eth }
    } catch (err) {
      console.error(ex.message);
    }
  }
})(1)

This works pretty much the same except

  1. It waits one minute once the server starts
  2. It will always wait one minute between requests

It's also a lot easier to understand and read, and since you use async/await, it sticks to a single paradigm (instead of using async/await and callbacks for the setTimeout).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
c1moore
  • 1,827
  • 17
  • 27
0

I think jfriend00's answer already solved the problem. For readability sake, I advise you a less functional approach:

;(async function ticker (minutes) {
  while(true) {
    await new Promise(res => setTimeout(res, minutes * 60 * 1000));
    try {
      const res = await coingecko.global()
      const { market_cap_percentage } = res.data.data
      dominance = { btc: market_cap_percentage.btc, eth: market_cap_percentage.eth }
      console.log({ dominance })
    } catch (ex) {
      console.log(ex.message)
    }
  }
})(1)
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
0

Here is an overly complicated and probably not suited solution, setInterval. I just wanted to show how we can use setInterval to achieve the same behaviour.

const fetch = require('isomorphic-fetch')
const url = 'https://jsonplaceholder.typicode.com/posts';


const closeThis = () => {
  let ids = [];
  async function executeThisShiz(minutes) {
    ids.forEach((id) => {
      clearInterval(id);
    })
    try {
      let res = await fetch(url);
      res = await res.json();
      console.log('yay')
      return res;
    } catch(e) {
        // maybe abort the timer idk?
        console.log('error', e);
    } finally {
      id = setInterval(() => {
          executeThisShiz(minutes);
      }, minutes*60*1000);
      ids.push(id);
    }
  }
  return {
    ids,
    executeThisShiz
  }
}

const {executeThisShiz} = closeThis();
executeThisShiz(0.1);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Rohit Kashyap
  • 1,553
  • 1
  • 10
  • 16