1

I read some example how to refactor the code to fit the style of promise but it seems they may need some help from other promise libraries.

My issue is that I have multiple async tasks which some of them may rely on some information from another call. So my code looks like that:

Let's say I have a Library table. A Library table has many Books. A Book has many Pages. A Page has many photos.

I am also using LoopBack to create the instance for these tables.

I have an array of library objects in this format.

{
    "location": "Hong Kong Island",
    "size": "25sq meter",
    "availableBooks" : [
        {
            "bookName": "Harry Potter",
            "pages": 235,
            "author": "J R Rowling"
            "pages": [
                {
                    "page": 1,
                    "content": "XXX",
                    "photos": [
                        {
                            "urlLink": "https://someLink",
                            "size": "360X250"
                        },
                        {
                            "urlLink": "https://google",
                            "size": "650X250"
                        }

                    ]
                }
                ,
                {
                    "page": 2,
                    "content": "XXX"
                }
            ]
        },
        {
            "bookName": "Lord Of The Rings",
            "pages": 335,
            "author": "J. R. R. Tolkien"

        }

    ]
} 

For pseudo code, it looks like this.

for(something){
    asyncCallA()
    .then((A) => {
        for(something){
            asyncCallB(A)
                .then((B) => {
                    for(something){
                        asyncCallC(B)
                        .then((C) => {
                            for(something){
                                asyncCallD(C)
                            }
                        })
                    }

                })
        }
    })

}

For common cases, I understand how to use promise to chain the async operations. But in this case where the async calls are dependent to each other and invovle a lot of for loop, I have no idea how to flatten the call. Can anyone please provide some insights? Thanks.

Ken Kwok
  • 388
  • 3
  • 19
  • 1
    Not certain what issue is with code at Question? – guest271314 Nov 05 '17 at 15:19
  • Since the async calls are dependent on each other, I have no idea how to flatten the code. If I do in this way, wouldnt it oppose to the idea of promise because promise is designed to flatten the callback hell? – Ken Kwok Nov 05 '17 at 15:22
  • You only have code with the "arrow pattern" because you don't chain the promises: `asyncCallB(A).then(asyncCallC).then(asyncCallD)`. And depending on what `something` is, you could also use `Promise.all` instead. – str Nov 05 '17 at 15:24
  • if every chain in the loop is independent of the other iterations, you can use `Promise.all` to let all iterations run (sort of) in parallel. Other than that, return a value from the `then` callbacks instead of chaining a `then` directly onto the result so you can do something like .then().then() instead of .then(then()) – Touffy Nov 05 '17 at 15:25
  • 2
    What do you mean by "flatten"? What is the actual code that you are using? See https://stackoverflow.com/help/mcve – guest271314 Nov 05 '17 at 15:26
  • I am trying to avoid promise.then inside promise.then. I am trying to update the database table based on object with format like this. { "a": "b", "someArray": [ { "some": "field", "anotherArray": [ { "another": "field", "theOtherArray": [ ] } ] }, ] } Each object inside an array is a table row, so the tables are dependent. – Ken Kwok Nov 05 '17 at 15:35
  • 2
    It's very difficult to provide a great answer when the code is just pseudo code. Detailed answers require REAL code that shows the REAL issues you have. – jfriend00 Nov 05 '17 at 15:37
  • Your example doesn't show any dependencies other than dependencies on the value of the variable passed into each individual `then`. If it's actually more involved then that, then your example should reflect that (otherwise, what's the point of the example?). As jfriend said, preferably it should be something more relatable than nondescript pseudocode. – JLRishe Nov 05 '17 at 15:47
  • No clear problem statement exists at text of original Question – guest271314 Nov 05 '17 at 15:50
  • Sorry I updated the example. Just found I forgot to add some loop. So I have a Library table. A Library table has many Books. A Book has many Pages. A Page has many photos. I separated them into several tables. Each photo has the unique id of a page and each page a unique id referencing the book and each book has a unique id referencing the library. So for every library object, I have to update all the books available and for each book I have update all the pages and for each page I have to update all the images available. – Ken Kwok Nov 05 '17 at 16:02
  • I added the pseudo code. Would it be better. But you are right. Maybe blue bird can help. – Ken Kwok Nov 05 '17 at 16:15
  • 1
    Iterating a table that has books that has pages that has images just requires nested loops. Unless you write some generic function for iterating all nodes of an arbitrarily nested data structure, there is no more declarative way to write your code that a series of nested loops. If that's what your code objective is, then that's the code you write. Personally, I'd probably break it into a series of resuable functions `iterateTables()`, `iterateBooks()`, `iteratePages()`, `iterateImages()` and so on and then it will likely look cleaner. – jfriend00 Nov 05 '17 at 17:04

2 Answers2

5

It's not clear exactly what you're asking, but inside the for loop, you can replace nesting with chaining. So something like this:

       asyncCallB(A)
        .then((B) => {
            asyncCallC(B)
            .then((C) => {
                asyncCallD(C)
            })
        })

Can be replaced with this:

       asyncCallB(A).then(B => {
         return asyncCallC(B);
       }).then(C => {
         return asyncCallD(C);
       });

If you want one for loop iteration to wait for the previous one to finish (though you don't show any dependency from one loop iteration to the next that requires that), then you have to either use await with the main loop promise (and the parent function has to be declared async) or you have to switch to a different way of manually iterating other than a for loop.

    for(...) {
       let loopResult = await asyncCallB(A).then(B => {
         return asyncCallC(B);
       }).then(C => {
         return asyncCallD(C);
       });
    }

Or, many times, the various iterations of the loop can proceed in parallel, but you want to know when they are all done:

    let promises = [];
    for(...) {
       let p = asyncCallB(A).then(B => {
         return asyncCallC(B);
       }).then(C => {
         return asyncCallD(C);
       });
       promises.push(p)
    }
    Promise.all(promises).then(results => {
       // all promises in the loop done now
    });

I've also found the Bluebird promise library very useful for iterating as it has things like Promise.map(), Promise.each(), Promise.reduce() and Promise.mapSeries() that offer you some control over the iteration (including whether it's parallel or serial and how much concurrency you want to allow) and does more of the work for you.

Related answers:

Using Promises with fs.readFile in a loop

Proper while() loop for bluebird promises (without recursion?)

How to synchronize a sequence of promises?

Javascript while loop where condition is a promise

bluebirdjs promises wrapped inside a for loop

How to chain promises in for loop in vanilla javascript

How to chain an async ajax function in a loop

JavaScript: Perform a chain of promises synchronously

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • @kenkwok - I guess I'm now regretting putting any effort into trying to help you here with this answer because you seem to have now changed the question to be about something else besides what it was when I wrote my answer. That is NOT how you should be using stack overflow. You clarify your original question. You don't change it to be a completely different question. – jfriend00 Nov 05 '17 at 16:06
  • Well. I guess that is just because of my typo. I didnt add the loop that is supposed to be there. THAT is my original question it's just I didnt type correctly. – Ken Kwok Nov 05 '17 at 16:26
  • @KenKwok - Well, with the lack of specifics you've provide, I'd say what we've offered so far is about all we can do. All your question seems to really ask about is "flattening" which we've shown you as much as we can based on the info you provided. – jfriend00 Nov 05 '17 at 16:40
  • Thank you for your time. The last comment on the post helps. – Ken Kwok Nov 06 '17 at 03:34
1

If you are using ES7 you can use async/await. Here is an example:

for(something){
    let A = await asyncCallA();
    for(something){
        let B = await asyncCallB(A);
    }
}

Node.JS 7.4.0 and after supports this without any experimental flags.

itsundefined
  • 1,409
  • 2
  • 12
  • 32