0

I have this code to get some data from an API:

const data = async () => {
  const apiResponse = await dataFromAPI();
  return apiResponse;
}

The apiResponse will always be the same for a session where this code is run. I chose this design because I would like the opportunity to chain multiple functions to the API.

I added a cache variable to short-circuit future requests.

let dataCache;

const data = async () => {
  if (dataCache) return dataCache;
  const apiResponse = await dataFromAPI();
  dataCache = apiResponse;
  return dataCache;
}

However, calling data() twice in succession will make still two API requests.

data().then(response => console.log(response))
data().then(response => console.log(response))

This makes one API request, then uses the cache variable:

data().then(response => console.log(response))
setTimeout(() => {
  data().then(response => console.log(response))
}, 100)

How can I design a singleton for this?

8
  • 2
    Cache the promise, not the value it resolves to. Commented Mar 22, 2023 at 22:07
  • Right, cache the Promise. You can await the resolved Promise as many times as you want. Commented Mar 22, 2023 at 22:18
  • How would I cache the promise? If I assign the promise to a variable it will still call the API everytime Commented Mar 22, 2023 at 22:20
  • Are you in an environment where you can use top-level await? If so, this is easier. Commented Mar 22, 2023 at 22:23
  • No, editor throws an error @jfriend00 Commented Mar 22, 2023 at 22:24

2 Answers 2

1

I think you can work with the Promise as the thing you cache like this:

let dataCache;

const data = async () => {
  if (!dataCache) {
      dataCache = dataFromApi();
  }
  return await dataCache;
}

That way dataCache will be set to the Promise returned from the API call. If it's set, then it will be resolved already or it won't be; in either case, a second API call won't be made.

Sign up to request clarification or add additional context in comments.

3 Comments

Something to consider is how errors should be handled. Currently, they're cached as well, and the dataFromApi() call is never retried.
Btw, no need to await before the return. You don't even need to make the function async.
Sure, this was off the top of my head. You can move the async stuff wherever you want in this.
0

Pointy's answer is classical, you can even optionally wrap everything inside an iife or so. If you want something else to experiment with you can try async generators:

async function* data() {
    const res = await dataFromAPI();
    while(true){
        yield res
    }
}

let myData = data();
myData.next() //Promise with res
myData.next() //Different Promise with same res

3 Comments

Not my downvote, but this does indeed look experimental and not production-ready. The classical approach is much easier to understand and use. What's the advantage of using a generator and having to call a next() method?
1- it does not need a cache variable or pollute outer scope. 2-it does not add a new call to the stack with the cost of keeping the same scope in memory (if we do not count calling next, which I think would be optimized by engines). But someone of course needs to profile this to make sure.
The outer scope is "polluted" by myData. Sure, it's easy to solve with an IIFE, but so is the traditional solution. And you absolutely do need to count calling .next(), which does run code in the scope of the generator function. I doubt this is any more efficient, neither memory nor speed wise.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.