简体   繁体   中英

Javascript promises and recursion: is this a stack bomb?

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 data are not being de-allocated until the entire chain has finished

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 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM