2016-11-23 111 views
6

我有一个异步递归函数返回承诺,如果有更多的工作要做或返回结果数组,否则。在不涉及递归的情况下,它正确地返回数组,但是当递归存在时,数组是未定义的。该代码是JavaScript承诺递归

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

这被称为

foo(abcfile).then(function(result){ 
    console.log(result); 
}); 

如果我通过abcfile它没有resp.pages的方式,我得到的结果数组,但也有resp.pages,那么结果数组未定义。

+0

你对“页面”数据有什么期望?你不会在'if(resp.pages)'块中返回任何东西,并且在传给'forEach'的函数中使用的返回不起任何作用 – Phil

+1

当'resp.pages'为真时,你不返回任何东西,这等于'返回undefined'。 – Leo

回答

-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); 
} 
+0

这不会等待递归异步操作 – Phil

+0

@Phil我更新了现在做你想做的事情。 –

3

我认为你只是缺少if (resp.pages)块内返回的承诺

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

我想有可能是一个问题与作用域resultArrdoo功能外,所以也许尝试此

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" 
    ] 
] 

但与sp读,你会得到

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

我应该使用concat来创建扁平列表.. – ams

+0

@Phil我认为你的数组范围可能与承诺一起 – ams

+0

@ams这就是为什么我把它移动到我的编辑 – Phil

0

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

除了菲尔的回答,如果你想要执行的订单/序列页

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

这非常酷,如果OP的'asyncOperation'不能同时执行多个请求,但是你没有合并任何结果way – Phil

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(已解组),它是队列中的项目。

我已经使用了新的async/await语法,这对于处理这个问题特别好。我们不必手动构建任何承诺或处理任何手动链接。

我做自由使用传播语法的递归调用,因为我的可读性,但你可以很容易,如果你喜欢,更多的替换这些为concat电话acc.concat([data])xs.concat(pages)。 - 这是函数式编程,所以只需选择一个你喜欢的不可变操作并使用它。

最后,与其他使用Promise.all的答案不同,这将处理系列中的每个页面。如果一个页面有50个子页面,则Promise.all会尝试在并行中发出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获取现有页面标识符)。当并行处理页面时,这是非常棘手的(并且这个答案超出了范围)。

+0

很好的回答,很高兴看到'async'和'await'成为常态。我认为OP的代码试图导航文件系统,所以如果我们忽略链接(符号链接),循环引用*可能不是问题。另外,您的StackOverflow格式化滑雪板不在链条上! – Phil

+0

感谢您的反馈,@菲尔^ _ ^ – naomik