0

I have the following JS function that fetches data and put in a window.data property:


function fetchData() {
  if (window.data) {
    return Promise.resolve(window.data)
  } else {
    return fetch('http://example.com/data.js')
              .then(response => response.json())
              .then(json => window.data = json)
  }
}

The code works fine. However, when there are two concurrent calls to that function, the first call to fetchData will fire the fetch. Because the fetch can take some time, a second call to fetchData won't find anything in the window.data and fire the same fetch.

Question : How can I send an elegant pending signal to the second call and return the result as a resolved promise ? Unless a promise-based solution is not possible, is there any another approach I can dig ?

The ideal solution should not block the second block but instead deal with it asynchronously with promises

Regards

I tried a mutex-based solution but it seemed over complicated.

  • 1
    This is a very difficult design to program with because `fetchData()` sometimes returns the data and sometimes returns a promise. Probably, you should just always return a promise that resolves to your data. – jfriend00 Jul 28 '23 at 17:19
  • @jfriend00 thank for your suggestion. I edited the code snippet. However, I can't understand how a promise can solve that problem ? Regards – howlettepackage Jul 28 '23 at 17:31
  • Change `return ….then(json => window.data = json);` to `return window.data = ….;` (and then rename `data` to `dataPromise`) – Bergi Jul 28 '23 at 17:33

1 Answers1

-1

Since the result is being attached to window, and would-be caller may check that first. But more generally, return a promise from both branches of the conditional. That promise is your elegant representation of something pending. When the data is present, the resolve branch runs immediately...

// callers should await this function
async function fetchData() {
  if (window.data) {
    return Promise.resolve(window.data);
  } else {
    return fetch('http://example.com/data.js')
              .then(response => response.json())
              .then(json => window.data = json)
  }
}


// from anyplace in the code...
const theData = await fetchData();

// using older syntax, all callers should proceed in a `then()` closure...
fetchData().then(theData => {
  // use the data in only in this scope
});


edit A commenter suggests that the OP might be aiming to avoid a second call to fetch. One way is to use the promise itself as a sentinel value.

Restating (not endorsing using window, but for wherever the value is kept)...

async function fetchData() {
  if (!window.promiseForData) {
    window.promiseForData = fetch('http://example.com/data.js')
              .then(response => response.json())
              .then(json => window.data = json)
  }
  return window.promiseForData;
}

Callers who await (or then) the result of this function will either get the already resolved promise (which will harmlessly resolve again to the data), or the existing deferred result without triggering another fetch.

danh
  • 62,181
  • 10
  • 95
  • 136
  • I agree with "always return a promise", but your implementation doesn't solve the OPs problem: it should not fire a second `fetch()` request if a first one is already underway – Bergi Jul 28 '23 at 17:24