Javascript promises recursion

I have an asynchronous recursive function that returns a promise if there is more work to do or returns an array of results otherwise. If recursion is not involved, it returns the correct array, but when recursing, the array is undefined. The code

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

And what is it called

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

If I pass abcfile that does not have resp.pages, I get an array of results, but there is resp.pages, then the array of results is undefined.

+6
source share
4 answers

I think that you simply did not receive the promised promise in the if (resp.pages) block

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

I think there might be a problem with the resultArr outside the doo function, so maybe try this

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

To explain the use of the spread operator, look at it that way ...

Say you have three pages for a top file; page1 , page2 and page3 , and each of these sections is divided into several subpages, pagesArr will look like

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

and resultArr still looks like

 ['top'] 

If you use concat without a spread operator, you will get

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

But with the spread you get

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

To make sure this works, I will create a fake and fakeAsyncOperation that will read data from the dataset asynchronously. To accurately model your data, each request from a fake data set returns a response with the data and pages fields.

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

Next we have your foo function. I renamed doo to enqueue because it works as a queue. It has two parameters: acc for tracking accumulated data and xs (destructed), which are the elements in the queue.

I used the new async/await syntax, which makes it especially nice to solve this. We do not need to manually create any Promises or deal with any manual .then chain.

I freely used the distribution syntax in the recursive call because I have readability, but you can easily replace them for the concat calls acc.concat([data]) and xs.concat(pages) if you like it more. is functional programming, so just select the immutable operation that you like and use it.

Finally, unlike other answers that use Promise.all , this will process each page sequentially. If there were to be 50 subpages on the page, Promise.all would try to make 50 requests in parallel, and this might be undesirable. Converting a program from parallel to serial is not necessarily simple, so this is the reason for providing this answer.

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

Exit

 [ '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' ] 

Note

I am glad that the foo function in my solution is not too far from your original - I think you will appreciate it. They use an internal helper function to cyclize and approach the problem in a similar way. async/await keeps the code pretty good and well readable (imo). All in all, I think this is a great solution for a rather complex problem.

Oh, and don't forget about circular links . There are no round links in my dataset, but if page 'a' should have pages: ['b'] and page 'b' had pages: ['a'] , you can expect infinite recursion. Since this answer processes the pages one at a time, this would be very easy to fix (by checking the accumulated acc value for the existing page id). It is much more difficult (and beyond the scope of this answer) to handle parallel page processing.

+1
source

The problem is mixing async / sync operations in if (resp.pages) branches if (resp.pages) . Basically, you'll have to return a promise from a subsequent callback if you want the promise chain to work properly.

In addition to Phil's answer, if you want to execute pages in order / sequence

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

The problem is that you are not returning anything when there are pages.

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

Source: https://habr.com/ru/post/1012605/


All Articles