javascript承诺递归

时间:2016-11-23 23:45:22

标签: javascript recursion es6-promise

我有一个异步递归函数,如果还有更多工作需要返回promise,否则返回结果数组。如果不涉及递归,它会正确返回数组,但是当递归时,数组是未定义的。代码是

instanceof

这就是所谓的方式

function foo(filepath) {

  var resultArr = [];

  function doo(file) {
        return asyncOperation(file).then(resp => {
            resultArr.push(resp.data);
            if (resp.pages) {
                var pages = resp.pages.split(',');
                pages.forEach(page => {
                    return doo(page);
                });
            } else {
                return resultArr;
            }
        });
    }

    return doo(filepath);
}

如果我传递没有resp.pages的abcfile,我得到结果数组,但是有resp.pages,那么结果数组是未定义的。

4 个答案:

答案 0 :(得分:3)

我认为你只是错过了if (resp.pages)区块中的退货承诺

if (resp.pages) {
    return Promise.all(resp.pages.split(',').map(page => doo(page)))
        .then(pagesArr => {
            return resultArr.concat(...pagesArr)
        })
}

我认为在resultArr函数之外设置doo可能存在问题,所以也许可以试试这个

function foo(filepath) {
    function doo(file) {
        return asyncOperation(file).then(resp => {
            const resultArr = [ resp.data ]
            if (resp.pages) {
                return Promise.all(resp.pages.split(',').map(page => doo(page)))
                    .then(pagesArr => resultArr.concat(...pagesArr))
            } else {
                return resultArr
            }
        })
    }

    return doo(filePath)
}

要解释传播运算符的使用,请以这种方式看待它......

假设您有一个文件top的三个页面; page1page2page3并且每个都解析了每个子页面,pagesArr看起来像

[
  ['page1', 'page1a', 'page1b'],
  ['page2', 'page2a', 'page2b'],
  ['page3', 'page3a', 'page3b']
]
到目前为止,

resultArr看起来像

['top']

如果您使用concat而不使用点差运算符,则最终会使用

[
  "top",
  [
    "page1",
    "page1a",
    "page1b"
  ],
  [
    "page2",
    "page2a",
    "page2b"
  ],
  [
    "page3",
    "page3a",
    "page3b"
  ]
]

但随着传播,你得到了

[
  "top",
  "page1",
  "page1a",
  "page1b",
  "page2",
  "page2a",
  "page2b",
  "page3",
  "page3a",
  "page3b"
]

答案 1 :(得分:1)

要验证这是否有效,我将创建一个fake数据集,以及一个fakeAsyncOperation,它会异步读取数据集中的数据。要密切建模数据,假数据集中的每个查询都会返回包含datapages字段的响应。

let fake = new Map([
  ['root',    {data: 'root',    pages: ['a', 'b', 'c', 'd']}],
  ['a',       {data: 'a',       pages: ['a/a', 'a/a']}],
  ['a/a',     {data: 'a/a',     pages: []}],
  ['a/b',     {data: 'a/b',     pages: ['a/b/a']}],
  ['a/b/a',   {data: 'a/b/a',   pages: []}],
  ['b',       {data: 'b',       pages: ['b/a']}],
  ['b/a',     {data: 'b/a',     pages: ['b/a/a']}],
  ['b/a/a',   {data: 'b/a/a',   pages: ['b/a/a/a']}],
  ['b/a/a/a', {data: 'b/a/a/a', pages: []}],
  ['c',       {data: 'c',       pages: ['c/a', 'c/b', 'c/c', 'c/d']}],
  ['c/a',     {data: 'c/a',     pages: []}],
  ['c/b',     {data: 'c/b',     pages: []}],
  ['c/c',     {data: 'c/c',     pages: []}],
  ['c/d',     {data: 'c/d',     pages: []}],
  ['d',       {data: 'd',       pages: []}]
]);

let fakeAsyncOperation = (page) => {
  return new Promise(resolve => {
    setTimeout(resolve, 100, fake.get(page))
  })
}

接下来我们有foo功能。我已将doo重命名为enqueue,因为它的作用类似于队列。它有两个参数:acc用于跟踪累积的数据,xs(destructured)是队列中的项目。

我使用了新的async/await语法,这使得处理这个问题特别好。我们不必手动构建任何Promises或处理任何手动.then链接。

我在递归调用中自由使用了扩展语法,因为它具有可读性,但是如果您更喜欢,可以轻松替换concat调用acc.concat([data])xs.concat(pages)。 - 这是函数式编程,所以只需选择你喜欢的不可变操作并使用它。

最后,与使用Promise.all的其他答案不同,这将处理系列中的每个页面。如果一个页面有50个子页面,Promise.all将尝试在 parallel 中发出50个请求,这可能是不受欢迎的。将程序从并行转换为串行不一定是直截了当的,因此这就是提供这个答案的原因。

function foo (page) {
  async function enqueue (acc, [x,...xs]) {
    if (x === undefined)
      return acc
    else {
      let {data, pages} = await fakeAsyncOperation(x)
      return enqueue([...acc, data], [...xs, ...pages])
    }
  }
  return enqueue([], [page])
}

foo('root').then(pages => console.log(pages))

输出

[ 'root',
  'a',
  'b',
  'c',
  'd',
  'a/a',
  'a/a',
  'b/a',
  'c/a',
  'c/b',
  'c/c',
  'c/d',
  'b/a/a',
  'b/a/a/a' ]

说明

我很高兴我的解决方案中的foo功能与原始功能相差太远 - 我想你会很感激。它们都使用内部辅助函数进行循环,并以类似的方式处理问题。 async/await使代码保持平坦且高度可读(imo)。总的来说,我认为对于一个复杂的问题,这是一个很好的解决方案。

哦,不要忘记循环引用。我的数据集中没有循环引用,但如果页面'a'pages: ['b']而页面'b'pages: ['a'],则可以预期无限递归。由于此答案以串行方式处理页面,因此这很容易修复(通过检查现有页面标识符的累计值acc)。在处理并行处理页面时,处理这个问题比较复杂(并且超出了这个答案的范围)。

答案 2 :(得分:0)

这里的问题是在if (resp.pages)分支中混合异步/同步操作。基本上,如果您希望承诺链按预期工作,您将不得不从回调中返回承诺。

除了Phil的回答,如果你想按顺序/顺序执行页面

if (resp.pages) {
  var pages = resp.pages.split(',');
  return pages.reduce(function(p, page) {
    return p.then(function() {
        return doo(page);
    });
  }, Promise.resolve());
}

答案 3 :(得分:-1)

问题是,当有页面时,你不会返回任何内容。

function foo(filepath) {

  var resultArr = [];

  function doo(file, promises) {
        let promise = asyncOperation(file).then(resp => {
            resultArr.push(resp.data);
            if (resp.pages) {
                var pages = resp.pages.split(',');
                var pagePromises = [];
                pages.forEach(page => {
                    return doo(page, pagePromises);
                });

                //Return the pages
                return pagePromises;
            } else {
                return resultArr;
            }
        });

        //They want it added
        if ( promises ) { promises.push( promise ); }

        //Just return normally
        else { return promise; }
    }

    return doo(filepath);
}