1

I'm relatively new to Javascript, and I'm a bit confused about promises and their scope.

Let's say I have the following method:

function fetchAllData(){
    //Calling backend to fetch data, returns a promise
    backend.getMoreData(localData.length, 1000).then(function(data){
        //Store fetched data
        for(let i = 0; i < data.length; i++){
            localData.push(data[i]);
        }

        //If there's more data, invoke fetchData() again
        if(data.length > 0){
            log("Fetching more data...");
            fetchAllData();
        } else{
            log("Fetched all data!");
        }
    } );
}

Basically the method operates like this:

  1. Fetches some data from a backend API (the API returns a promise)
  2. Once the promise is fulfilled, the data is added to a local variable
  3. If there's more data to fetch, the function is called recursively to fetch more, until all data has been fetched

My question is: is this a potential "stack bomb"? Or, thanks to the promise mechanism, the invoking function is popped from the stack before the "then()" method is invoked?

I ask because I'm seeing highest than expected memory usage and some browser crashes when using this, almost making me suspect the various instances of dataare not being de-allocated until the entire chain has finished

Master_T
  • 7,232
  • 11
  • 72
  • 144
  • 2
    No, that shouldn't be a stack bomb. The stack should be cleared each time because the callbacks are being called asynchronously and I don't see any closure variables that would linger around indefinitely. Is there any possibility that the individual elements of the `data` arrays have references to something that's hogging memory, e.g. they have backreferences to the `data` arrays, or to some data about the request, or something? About how big would you expect `localData` to be when everythign is retrieved? – JLRishe Jan 22 '18 at 14:35
  • how does `backend.getMoreData` fetch the "next" data? – ie, if data is paginated, you'll generally specify the page to query – Mulan Jan 22 '18 at 14:54
  • Possible duplicate of [Building a promise chain recursively in javascript - memory considerations](https://stackoverflow.com/questions/29925948/building-a-promise-chain-recursively-in-javascript-memory-considerations) – Roamer-1888 Jan 23 '18 at 02:16

1 Answers1

0

There's nothing wrong with writing recursive asynchronous functions. But let's take a step back and ask two important questions:

  1. How do you know when it's safe to read the global localData?
  2. How does backend.getMoredata know to fetch the "next" data? Ie, what's stopping it from repeating the first query?

To answer 1, we must wait until the last data is fetched and then finally resolve the last Promise – it's at that exact point we know we have all of the data.

To answer 2, you're probably using another global variable that you didn't include in your code paste. But in general, you'll want to avoid introducing new globals into your code. Instead ...

Use function parameters! We solve both problems by adding an optional parameter and defining a default value. Now, localData is actually local data, and it's obvious how backend.getMoreData queries the "next" data chunk

const fetchAll = (page = 0, localData = []) =>
  backend.getMoreData(page).then(data =>
    data.page === data.pageCount
       ? localData
       : fetchAll(page + 1, localData.concat(data.results)))

Here's a complete demo. I stubbed out backend and a DB constant so we can see it working in both the success and error scenarios

const DB = 
  { 0: { results: [ 'a', 'b', 'c' ], page: 1, pageCount: 3 }
  , 1: { results: [ 'd', 'e', 'f' ], page: 2, pageCount: 3 }
  , 2: { results: [ 'g' ], page: 3, pageCount: 3 }
  }

const backend =
  { getMoreData: page =>
      DB[page] === undefined
        ? Promise.reject(Error(`Data not found`))
        : Promise.resolve(DB[page])
  }
  
const fetchAll = (page = 0, localData = []) =>
  backend.getMoreData(page).then(data =>
    data.page === data.pageCount
       ? localData
       : fetchAll(page + 1, localData.concat(data.results)))
       
// query all data starting on page 0
fetchAll().then(console.log, console.error)
// [ 'a', 'b', 'c', 'd', 'e', 'f' ]

// query all data starting on page 5 (out of bounds)
fetchAll(5).then(console.log, console.error)
// Error: Data not found
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • Looking again, I see you're using `localData.length`, `1000` as arguments to `backend.getMoreData`. The idea here is similar, but your program probably works fine. You can still use this answer to tame your `localData`. – Mulan Jan 22 '18 at 15:22
  • You are correct, backend.getData() accepts two parameters: starting index and how many entries to fetch. I stop when 0 records are returned (end of data). Thanks for your answer. – Master_T Jan 23 '18 at 08:22