0

I have the below sequence of the chained loop which I want to return using promises but I get the response before the forEach is executed in my code... Can anyone tell me where I am going wrong... I would like to do it using native Promises and would not prefer to use await/async so I get a better understanding of how Promises function.

Details.find(function(err,details){
    if(err){
        res.send(err)
      }else{
          console.log("----------First Promise------------")
          return details
      }
}).then(result1 => {
    result1.forEach(function(item){
        renderData = {}
        OrgChart.findOne({GID:item.gid},function(err,detail){
                console.log("Detail is ------> " + detail.DISPLAY_NAME)
                if(err){
                    res.send(err)
                }else{
                    return detail.DISPLAY_NAME
                }
            }).then( result2 => {
                renderData.gid = result2.DISPLAY_NAME
                renderData.pageouttime = item.pageouttime
                renderData.createdAt = item.createdAt
                renderData.oncall = item.oncall
                renderData.comments = item.comments
                renderData.actionLink = item._id.toString()              
                console.log(renderData)
                dataArr.push(renderData)

            })      
    })
}).then(result3 => {
    console.log("Final Result is ----> " + result3)
    response.data = result3
    res.json(response) 
}) 

Inside the forEach, I want to get a value using a value of the row. I am new to node js and Promises... I want to achieve something like below but using Promises.

Example sequence I want to Achieve through Promises

var someArray = []
var DetailsObj = Details.find()
DetailsObj.each(function(item){
   var newMap = {}
   newMap.prop1=item.prop1
   newMap.prop2 = item.prop2
   newMap.prop3 = OrgChart.find({id:item.prop3}).displayName
   someArray.push(newMap)
})

Please, can anyone let me know where I am going wrong?

Update 1(Not Working)

return Promise.all(result1.map(function(item){
        renderData = {}
        OrgChart.findOne({GID:item.gid},function(err,detail){
                console.log("Detail is ------> " + detail.DISPLAY_NAME)
                if(err){
                    res.send(err)
                }else{
                    return detail.DISPLAY_NAME
                }
            }).then(result2 => {
                renderData.gid = result2.DISPLAY_NAME
                renderData.pageouttime = item.pageouttime
                renderData.createdAt = item.createdAt
                renderData.oncall = item.oncall
                renderData.comments = item.comments
                renderData.actionLink = item._id.toString()              
                console.log(renderData)
                dataArr.push(renderData)
            })      
    }))

Still i get an empty array

Update 2 (Added return from the map callback--- Still not Working)

return Promise.all(result1.map(function(item){
        renderData = {}
        OrgChart.findOne({GID:item.gid},function(err,detail){
                console.log("Detail is ------> " + detail.DISPLAY_NAME)
                if(err){
                    res.send(err)
                }else{
                    return detail.DISPLAY_NAME
                }
            }).then(result2 => {
                renderData.gid = result2.DISPLAY_NAME
                renderData.pageouttime = item.pageouttime
                renderData.createdAt = item.createdAt
                renderData.oncall = item.oncall
                renderData.comments = item.comments
                renderData.actionLink = item._id.toString()              
                console.log(renderData)
                dataArr.push(renderData)
            }) 
        return dataArr             
    })
)

Update 3(Updated after returning from the then() callback in the Promise.all() block --- Still not Working)

  return Promise.all(result1.map(function(item){
        renderData = {}
        OrgChart.findOne({GID:item.gid},function(err,detail){
                console.log("Detail is ------> " + detail.DISPLAY_NAME)
                if(err){
                    res.send(err)
                }else{
                    return detail.DISPLAY_NAME
                }
            }).exec().then(result2 => {
                renderData.gid = result2.DISPLAY_NAME
                renderData.pageouttime = item.pageouttime
                renderData.createdAt = item.createdAt
                renderData.oncall = item.oncall
                renderData.comments = item.comments
                renderData.actionLink = item._id.toString()              

                return renderData
            })            
    })
)
Apoorv
  • 622
  • 8
  • 22
  • you can try `Promise.all()`, insted of using foreach, insert your queries into an array, and process it through `Promise.all()` https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all – Saikat Chakrabortty Jan 30 '18 at 13:17
  • Tried but still no luck ... have put the updated code in that i tried – Apoorv Jan 30 '18 at 13:56
  • 1
    @Apoorv Your update misses to `return` the promise from the `map` callback – Bergi Jan 30 '18 at 14:00
  • @Bergi .. Added the return statement from the map callback ... but still no luck ... have updated above .... i am still getting the below response {"status":200,"data":[[],[],[],[],[],[],[],[],[]],"message":null} – Apoorv Jan 30 '18 at 14:30
  • I said you need to `return` *the promise* from the callback, as that is what `Promise.all` will be waiting for. It won't wait for an array that is still empty. Just drop the `dataArr` and resolve your promise with the `renderData` (by returning it from the `then` callback) – Bergi Jan 30 '18 at 14:38
  • i'd recommend you make a proper answer Bergi, it'll make things easier. I also think that promise.all will be the solution for this specific issue – Daniel Netzer Jan 30 '18 at 14:57
  • @Bergi I made the change suggested by you .... but still i am getting the same response "data":[null,null,null,null,null,null,null,null,null], – Apoorv Jan 30 '18 at 15:15
  • Is this [sails findOne](https://sailsjs.com/documentation/reference/waterline-orm/models/find-one)? – HMR Jan 30 '18 at 18:18
  • 1
    Also one tip; `not working` can only be answered by `you're doing something wrong`. not very helpful for anyone. if something is `not working` then you usually expect something but something else happens. You should provide this information. Like I expected .... but got error ... or got ... instead. – HMR Jan 30 '18 at 18:50

2 Answers2

2

First of all, you should properly promisify your functions:

function findDetails() {
    return new Promise((resolve, reject) => {
        Details.find((err, details) => {
            if (err) reject(err);
            else resolve(details);
        });
    });
}
function findChart(gid) {
    return new Promise((resolve, reject) => {
        OrgChart.findOne({GID:item.gid}, (err, detail) => {
            if (err) reject(err);
            else resolve(detail);
        });
    });
}

or, as your library seems to support for simplicity (notice we're not passing any callbacks!):

function findDetails() {
    return Details.find().exec();
}
function findChart(gid) {
    return OrgChart.findOne({GID:item.gid}).exec();
}

Now we can build the promise chain from those (notice all the return statements!):

findDetails().then(details =>
    console.log("----------First Promise------------");
    return Promise.all(details.map(item => {
//  ^^^^^^
        return findChart(item.gid).then(detail => 
//      ^^^^^^
            console.log("Detail is ------> " + detail.DISPLAY_NAME)
            const renderData = {
                gid: detail.DISPLAY_NAME,
                pageouttime: item.pageouttime,
                createdAt: item.createdAt,
                oncall: item.oncall,
                comments: item.comments,
                actionLink: item._id.toString(),
            };
            console.log(renderData);
            return renderData;
//          ^^^^^^
        });
    }));
}).then(dataArr => {
    console.log("Final Result is ----> " + dataArr);
    response.data = dataArr;
    res.json(response);
}, err => {
    res.send(err);
});
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I'm not familiar with Sails and not sure if that is actually the api that OP is using but [sails documentation](https://sailsjs.com/documentation/reference/waterline-orm/queries#promises) say that promises are an alternative to `exec` so `find` and `findOne` should return a promise without the `exec` – HMR Jan 30 '18 at 18:45
  • 1
    @HMR Thanks, even simpler then. In any case the point I was trying to make is that no callbacks should be passed to `find`(`One`), only to `then`. – Bergi Jan 30 '18 at 18:59
  • @Bergi Thanks for your solution ... it worked just the way i wanted it to ..... i finally got what you are trying to explain .... if i am not wrong but you mean to say that code block with `findOne` is not behaving as a promise at all ... and after the first block my code is returning a blank array... i really need to get my concepts of Promises in track .... Thanks all – Apoorv Jan 30 '18 at 19:18
0

If it is Sails you are using (should add the tag in the question and mention it) then according to the documentation it supports promises. But the documentation says that promises are an alternative to exec So your code should look like this:

Details.find()
.then(
  result =>
    Promise.all(
      result.map(
        item=>
          OrgChart.findOne({GID:item.gid})
          .then(//here is where your code is confusing
            // does detail have detail.DISPLAY_NAME.DISPLAY_NAME?
            // I assume it has not
            details=>({
              gid : details.DISPLAY_NAME,
              pageouttime : item.pageouttime,
              createdAt : item.createdAt,
              oncall : item.oncall,
              comments : item.comments,
              actionLink : item._id.toString()
            })
          )
      )
  )  
)
.then(
  results=>{
    console.log("results are:",results);
  }
)
.catch(
  err=>console.error("Something went wrong:",err)
);
HMR
  • 37,593
  • 24
  • 91
  • 160
  • No i am not using Sails... the part you are referring to is mongoose syntax ... and yes i should have atleast tagged mongooose – Apoorv Jan 30 '18 at 19:11
  • @Apoorv Ok, should be fine then, sales and mongoose are similar. [Query is a promise](http://mongoosejs.com/docs/queries.html): `A Query has a .then() function, and thus can be used as a promise.` but [exec](http://mongoosejs.com/docs/api.html#query_Query-exec) also returns a promise. As Bergi stated; you don't need to provide callback functions, only resolve and reject listeners. – HMR Jan 30 '18 at 19:18
  • In Mongoose queries are not promises but they have a `.then()` function for `yield and async/await` ... I am not sure what that means .... but if we want a full/fledge promise the documentation says to use `exec()` .....http://mongoosejs.com/docs/4.x/docs/promises.html – Apoorv Jan 30 '18 at 19:22
  • @Apoorv The 5.0.2 documentation literally says: `A Query has a .then() function, and thus can be used as a promise.` is the code provided in the answer giving you errors or unexpected results? I'd try running it in a single file first instead of part of a express like application so you can just run it from the command prompt and debug it (`node --inspect-brk index.js` open deftools in chrome and click on the node icon) – HMR Jan 30 '18 at 19:29
  • Yes i just saw that in 5.0.2... but i am currently using 4.13.0 .... i havent tried your solution ... .but will surely try it out .... Thanks again for your help – Apoorv Jan 30 '18 at 19:36