非常大的递归javascript

非常大的递归javascript,javascript,recursion,promise,Javascript,Recursion,Promise,我有这段代码,它使用递归计算数组元素和,但是当数组太大时,它抛出的最大调用堆栈大小超过了错误 var a = new Array(10 ** 7); a.fill(0); function sum(arr, i = 0) { if(i === arr.length) { return 0; } return arr[i] + sum(arr, i + 1); } sum(a); 因此,我需要以某种方式更改它,使其在所有情况下都能正常工作,并且我认为我可以使它与承诺

我有这段代码,它使用递归计算数组元素和,但是当数组太大时,它抛出的最大调用堆栈大小超过了错误

var a = new Array(10 ** 7);
a.fill(0);

function sum(arr, i = 0) {
  if(i === arr.length) {
      return 0;
  }

  return arr[i] + sum(arr, i + 1);
}

sum(a);
因此,我需要以某种方式更改它,使其在所有情况下都能正常工作,并且我认为我可以使它与承诺异步工作,但它总是返回承诺挂起

var a = new Array(10 ** 7);
a.fill(0);

var sum = (arr, i = 0) => new Promise((resolve) => {
  setTimeout(() => {
    if(i === arr.length) {
      return resolve(0);
    }

    return  sum(arr, i + 1).then(el => el + arr[i]);
  }, 0);
});

sum(a);
我怎样才能修好它


任何帮助都将不胜感激

您需要使用迭代过程而不是递归过程。因此,您不需要累积调用,而是计算每次迭代调用的总和。这可以通过使用一个helper函数来实现,该函数将获得
(sum,array,currentValue)

您只需要解决i为arr.length的情况,因此所有链接承诺将永远挂起。Return不会自动为我们解决问题,因此需要明确:

var a=新数组(10);
a、 填充(0);
a[0]=3;
var总和=(arr,i=0)=>新承诺((解决)=>{
设置超时(()=>{
如果(i==arr.length){
决议(0);
}否则{
解析(求和(arr,i+1)。然后(el=>el+arr[i]);
}
}, 0);
});

sum(a).then(console.log)
如果出于任何原因需要使用递归,并且不介意花很长时间,那么可以使用超时;然而,我绝对不建议使用这个。我之所以发布它,主要是因为这是可能的

var arr = new Array(10 ** 7);
    arr.fill(0);

var len = arr.length,
    idx = 0,
    sum = 0,
    sumFn = () => {
      setTimeout(() => {
        sum += arr[idx];
        idx++;

        if (idx < len) {
            sumFn();
        } else {
            //complete
        }
      }, 1);
    }

sumFn();
var-arr=新数组(10**7);
arr.fill(0);
var len=阵列长度,
idx=0,
总和=0,
sumFn=()=>{
设置超时(()=>{
总和+=arr[idx];
idx++;
if(idx
我不知道为什么要使用承诺并使函数异步。但如果您需要解决这两种情况:

const sum = (arr, i = 0) => new Promise((resolve) => {
  setTimeout(() => {
    if(i === arr.length) {
      return resolve(0);
    }

    return  sum(arr, i + 1).then(el => resolve(el + arr[i]));
  }, 0);
});
现在,这也返回了一个承诺。一旦你开始异步,你就再也回不去了。您需要使用返回的承诺来获取返回值:

sum([1, 2, 3, 4]).then(return => console.log(return));
最好不要使它异步。ES6支持尾部递归,因此您可以这样做:

function sum(arr) {
    function helper(acc, i) {
        if (i<0) {
            return acc;
        }
        return helper(arr[i]+acc, i-1);
    }

    return helper(0, arr.length - 1);
}

setTimeout
不是堆栈溢出问题的解决方案。管理堆栈是对函数调用进行排序的问题。您可以通过多种方式来实现这一点,最直观的是
循环
/
重复
蹦床

const循环=f=>
{let acc=f()
while(acc&&acc.tag==重复)
acc=f(…acc.值)
返回acc
}
常量重复=(…值)=>
({tag:recur,values})
常量和=(xs=[])=>
循环((结果=0,i=0)=>//初始状态
i>=xs.length//退出条件
?结果//返回值
:recur(result+xs[i],i+1))//下一个状态
常数1千万=
Array.from(数组(1e7),(2;,n)=>n)
console.time('递归不慢')
console.log(总和(千万个数字))
console.timeEnd('递归不慢')
// => 49999995000000

//递归并不慢:2171ms
对于使用本机任务的问题,有一些解决方案。这一个是使用Promise来安排一个微任务:

(function() {
  function sum(arr, i = 0) {
    if(arr.length === i) {
        return Promise.resolve(0);
    }

    return Promise.resolve(null)
      .then(() => sum(arr, i + 1))
      .then((x) => x + arr[i]);
  }

  sum(a).then(s => console.log(s));
}());
但这将迫使引擎等待执行完成。因此,对于大型阵列,我不建议您在主线程上执行此操作

您还可以执行以下操作:

(function() {
  function sum(arr, i = 0) {
    if(arr.length === i) {
        return Promise.resolve(0);
    }

    return new Promise(resolve => {
      setTimeout(() => {
        sum(arr, i + 1).then(x => resolve(x + arr[i]));
      });
    });
  }

  sum(a).then(s => console.log(s));
}());
(function() {
  const defer = () => new Promise((resolve) => setTimeout(resolve));

  async function sum(arr, i = 0) {
    if(arr.length === i) {
        return 0
    }

    await defer();
    return await sum(arr, i + 1) + arr[i];
  }

  sum(a).then(s => console.log(s));
}());
然后,通过对此代码进行一些更改并使其更加优雅,我们可以执行以下操作:

(function() {
  function sum(arr, i = 0) {
    if(arr.length === i) {
        return Promise.resolve(0);
    }

    return new Promise(resolve => {
      setTimeout(() => {
        sum(arr, i + 1).then(x => resolve(x + arr[i]));
      });
    });
  }

  sum(a).then(s => console.log(s));
}());
(function() {
  const defer = () => new Promise((resolve) => setTimeout(resolve));

  async function sum(arr, i = 0) {
    if(arr.length === i) {
        return 0
    }

    await defer();
    return await sum(arr, i + 1) + arr[i];
  }

  sum(a).then(s => console.log(s));
}());
如果您的环境支持尾部递归,您可以将其更改为使用它:

更新
实际上,还有一种方法可以做到这一点。rxjs库提供了一个队列调度器,它可以帮助您使递归逻辑迭代,而无需对代码进行许多更改。我为你的
sum
方法创建了一个例子。

你为什么需要递归,难道你不能简化它吗?可能的重复:学习根当然没有坏处,我可以,但这是一项任务,我必须修改代码并使其适用于大数组。不要在这个任务中使用递归(在javascript中)。您正在过度复杂化这一点:
const sum=arr=>arr.reduce((a,b)=>a+b,0)
甚至在您的函数中?@AngelaHayrapetian当然不使用预构建,但您必须能够自己创建helpers@AliaksandrSushkevich对我必须让它异步工作,这就是为什么我尝试用承诺来做这件事,但我做了一些错误的事情“ES6支持尾部递归”,在我所知的任何实现中都没有。Babel也在一年前推出了该功能。另外,为什么在Promise构造函数中使用
setTimeout
?@user633183这是OPs代码。我刚刚添加了缺少的
resolve
。因为这是一项要求,所以它将适用于所有符合ES6的实现。Safari/iOS支持尾部递归,可能是最符合ES6的。现在你知道了。哇,新发现的对狩猎的尊重✊需要解释一下你写的代码以及它将如何解决这个问题。