【问题标题】:Javascript promises and recursion: is this a stack bomb?Javascript 承诺和递归:这是一个堆栈炸弹吗?
【发布时间】:2018-01-22 23:17:47
【问题描述】:

我对 Javascript 比较陌生,对 Promise 及其范围有些困惑。

假设我有以下方法:

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!");
        }
    } );
}

该方法基本上是这样操作的:

  1. 从后端 API 获取一些数据(API 返回一个承诺)
  2. 一旦履行承诺,数据就会添加到局部变量中
  3. 如果要获取更多数据,则递归调用该函数以获取更多数据,直到获取所有数据为止

我的问题是:这是一个潜在的“堆栈炸弹”吗?或者,由于promise机制,调用函数在调用“then()”方法之前从栈中弹出?

我之所以问,是因为我看到内存使用量高于预期,并且在使用它时一些浏览器崩溃,几乎让我怀疑 data 的各种实例在整个链完成之前不会被取消分配

【问题讨论】:

  • 不,这不应该是一个堆栈炸弹。每次都应该清除堆栈,因为回调是异步调用的,而且我看不到任何会无限期徘徊的闭包变量。 data 数组的各个元素是否有可能引用占用内存的东西,例如他们有对data 数组的反向引用,或者关于请求的一些数据,或者什么?当所有的东西都被检索到时,你预计localData 有多大?
  • backend.getMoreData 如何获取“下一个”数据? – 即,如果数据是分页的,您通常会指定要查询的页面

标签: javascript recursion promise


【解决方案1】:

编写递归异步函数并没有错。但让我们退后一步,问两个重要的问题:

  1. 您如何知道何时可以安全阅读全球 localData
  2. backend.getMoredata 如何知道获取“下一个”数据?即,是什么阻止它重复第一个查询?

要回答 1,我们必须等到最后一个数据被获取,然后最终解决最后一个 Promise - 正是在那个时候,我们知道我们拥有所有数据。

要回答 2,您可能正在使用另一个未包含在代码粘贴中的全局变量。但总的来说,您会希望避免在代码中引入新的全局变量。而是...

使用函数参数!我们通过添加可选参数和定义默认值来解决这两个问题。现在,localData 实际上是本地数据,很明显backend.getMoreData 如何查询“下一个”数据块

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

这是一个完整的演示。我删除了 backendDB 常量,以便我们可以看到它在成功和错误情况下都有效

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

【讨论】:

  • 再看一遍,我发现您使用localData.length1000 作为backend.getMoreData 的参数。这里的想法是相似的,但是您的程序可能运行良好。你仍然可以使用这个答案来驯服你的localData
  • 你是对的,backend.getData() 接受两个参数:起始索引和要获取的条目数。当返回 0 条记录(数据结束)时我停止。感谢您的回答。
猜你喜欢
  • 2018-10-29
  • 2015-07-03
  • 1970-01-01
  • 2013-06-16
  • 1970-01-01
  • 2015-05-15
  • 1970-01-01
  • 2018-03-02
相关资源
最近更新 更多