0

Here is the code:

function f1(id) {
    return new Promise((resolve, reject) => {
        if (id === 123132) {
            resolve([{id:1,name:"John"}, {id:10, name:"Chris"}])

        } else {
            reject(new Error("f1 fails - can't find info for id"))
        }


    })

}

function f2(gender) {
    return new Promise((resolve, reject) => {
        if (gender === "FEMALE") {
            resolve([{id:6, name:"Stacy"}, {id:1, name:"John"}, {id:13, name:"Veronica"}])
        } else {
            reject(new Error("f2 Fails"))
        }
    })

}



 function Test(User, N){
    return new Promise((resolve, reject)=>{
        f1(User.id).catch(err=>{

            console.log(err.message)
            //this api returns an error, so call the other one
            f2(User.gender).catch(err=>{
                console.log(err.message)
                reject(new Error("Both api calls have failed"))
            }).then(res=>{
                if (res.length<N){
                  reject(new Error("Not Enough Data..."))
                } else {
                    console.log("We are here..")
                    console.log(res)
                    resolve(res.slice(0,N).map(item=>item.name))
                }
            })
        })
        .then(res1=>{
            console.log("We should not be here but we are...")
            console.log(res1)
          if (res1.length<N){
              f2(User.gender).catch(err=>{
                    console.log(err.message)
                //   reject(new Error("Not Enough Data"))
                  throw new Error("Not Enough Data")
              }).then(res2=>{
                  res3 = filterDups2(res1, res2)
                  res3.length>=N ? resolve(res3.slice(0,N).map(item=>item.name)) :
                  reject(new Error("Not Enough Data"))
              })
          } else {
              console.log("Why are we not here...")
              resolve(res1.slice(0,N).map(item=>item.name))
          }
        })
    })
}


function filterDups2(list1, list2){
    jointRes = [...list1, ...list2]
    ans = Array.from(new Set(jointRes.map(item=>JSON.stringify(item)))).map(item=>JSON.parse(item))  
    return ans
  }

let user = { id: 123, gender: "FEMALE" }
let result = Test(user, 2)


result.then(res=> console.log(res)).catch(err=> {
    console.log("Inside result...")
    console.log(err.message)
})


Here is the output on the console:

f1 fails - can't find info for id
We should not be here but we are...
undefined
We are here..
[
  { id: 6, name: 'Stacy' },
  { id: 1, name: 'John' },
  { id: 13, name: 'Veronica' }
]

[ 'Stacy', 'John' ]

(node:2968) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'length' of undefined
    at /Users/daanishraj/Desktop/Front-End-Work/WDB-Feb-2021/JavaScript/Practice/interviews/zalando/forStackOverflow.js:50:20
(Use `node --trace-warnings ...` to show where the warning was created)
(node:2968) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
(node:2968) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

I don't understand why we have the following two things on the console

We should not be here but we are...
undefined

showing up before these outputs:

We are here..
[
  { id: 6, name: 'Stacy' },
  { id: 1, name: 'John' },
  { id: 13, name: 'Veronica' }
]
[ 'Stacy', 'John' ]

I understand that because the outermost catch block i.e. f1(U.id).catch() is resolved, this would make us step into the outermost then block i.e.f1(U.id).then(). But the sequence of these outputs suggest that we are stepping into the then block before the catch block is resolved.

  1. Why is this the case?
  2. Since we are indeed stepping into the then block, once res1 is deemed undefined, why doesn't the control move to the bottom of the then block where we would output Why are we not here...?

In general, if you could throw some light on the sequence of execution here, that would be great!

Dextra
  • 15
  • 5
  • You might be better off using `async/await`. Raw Promises are quite clunky and often lead to spaghetti code. – georg Apr 19 '21 at 17:39
  • Could you try to drastically reduce your examples to *just* demonstrate the issue. – Evert Apr 19 '21 at 19:29

1 Answers1

0

Why is this the case?

Because you haven't instructed the promise chain to wait for an asynchronous result from the catch handler. You'd need to return a promise from there for that.
Stepping into the then handler doesn't happen "before the catch block is resolved", the catch handler did already execute and did return undefined - that's what the promise chain continues with.

Why doesn't the control move to the bottom of the then block where we would output "Why are we not here..."?

Because right after logging undefined, you access res1.length, which causes the exception TypeError: Cannot read property 'length' of undefined. This rejects the promise, which is not handled anywhere, leading to the warning.


Now onto the real question: how to do this properly? You should avoid the Promise constructor antipattern! The .then() and .catch() calls already return a promise for the result of the handler, which you can and should use - no need to manually resolve or reject a promise, and no unhandled promise rejections because some inner promise is rejected but you fail to propagate this failure outside (notice the "Inside result..." handler should have been called when res1.length errored). Also I recommend to use .then(…, …) instead of .then(…).catch(…) (or .catch(…).then(…)) for conditional success/error handling.

function Test(User, N) {
    return f1(User.id).then(res1 => {
//  ^^^^^^
        if (res1.length < N) {
            return f2(User.gender).then(res2 => {
//          ^^^^^^
                const res3 = filterDups2(res1, res2)
                if (res3.length >= N)
                    return res3.slice(0,N).map(item => item.name)
//                  ^^^^^^
                else
                    throw new Error("Not Enough Data")
//                  ^^^^^
            }, err => {
                console.log(err.message)
                throw new Error("Not Enough Data")
//              ^^^^^
            })
        } else {
            return res1.slice(0,N).map(item => item.name)
//          ^^^^^^
        }
    }, err => {
        console.log(err.message)
        return f2(User.gender).then(res => {
//      ^^^^^^
            if (res.length < N) {
                throw new Error("Not Enough Data...")
//              ^^^^^
            } else {
                return res.slice(0,N).map(item => item.name);
//              ^^^^^^
            }
        }, err => {
            console.log(err.message)
            throw new Error("Both api calls have failed")
//          ^^^^^
        })
    })
}

Avoiding some code duplication:

function Test(User, N) {
    return f1(User.id).then(res1 => {
        if (res1.length >= N) return res1;
        return f2(User.gender).then(res2 => {
            return filterDups2(res1, res2)
        }, err => {
            console.log(err.message)
            return res1;
        })
    }, err => {
        console.log(err.message)
        return f2(User.gender).catch(err => {
            console.log(err.message)
            throw new Error("Both api calls have failed")
        })
    }).then(res => {
        if (res.length >= N)
            return res.slice(0,N).map(item => item.name)
        else
            throw new Error("Not Enough Data")
    })
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • thanks for your answer. What do you mean by: "Because you haven't instructed the promise chain to wait for an asynchronous result from the catch handler. You'd need to return a promise from there for that." Inside the outer catch blocks, I replaced the `reject` with `throw` and I replaced the `resolve` with `return` but I still see `We should not be here but we are... undefined ` on the console. – Dextra Apr 20 '21 at 09:50
  • Replacing `resolve`/`reject` with `return`/`throw` is not all I have done. Also remove `new Promise`, add extra `return`s, and reorder the handlers to `.then(…, …)`. Please try using the full code as I have written it in my answer. – Bergi Apr 20 '21 at 13:12
  • you say: "Stepping into the then handler doesn't happen "before the catch block is resolved", the catch handler did already execute and did return undefined..." I agree that the catch block was returning undefined. However, you are implying that the then handler is being stepped into after the catch block is resolved. Well, I don't understand. if this is true, why do we see: ``` We are here.. [ { id: 6, name: 'Stacy' }, { id: 1, name: 'John' }, { id: 13, name: 'Veronica' } ] ``` (these come from catch) being logged after the `undefined` in the console output? – Dextra Apr 22 '21 at 08:30
  • Because your `catch` handler is doing asynchronous stuff (the log statement is in an asynchronous callback), *after* it has returned `undefined`. Notice that a "catch block" cannot "resolve" - only a promise can be resolved - so I took this to mean "*the `catch` handler did return which causes the `.catch()` promise to resolve*". – Bergi Apr 22 '21 at 13:23