我有一个异步递归函数,如果还有更多工作需要返回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,那么结果数组是未定义的。
答案 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
的三个页面; page1
,page2
和page3
并且每个都解析了每个子页面,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
,它会异步读取数据集中的数据。要密切建模数据,假数据集中的每个查询都会返回包含data
和pages
字段的响应。
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);
}