1

I am new to promises, I am trying to use RSVP promises in Node.js with PostgreSQL and I am doing it wrong, most probably. Any suggestions on how to fix that or how to improve the code are appreciated.

What I try to achieve is: after receiving data - process the data to create SQL update queries and when they are ready - execute them. Data here is array of user ids.

What does not work: I get array of array of promises that doesn't resolve, I tried to resolve the array like so:

var promise4all = RSVP.all(
  updateQueries.map((innerPromiseArray) => {
    return RSVP.all(innerPromiseArray);
  })
);

promise4all.then((promiseGroupResult) => {
     // doesn't get here
});

But it didn't work also.

The code:

1) The function 'update' that receives data and calls function 'promiseQuery' to process the data:

const RSVP = require('rsvp');

let db;

const update = (data) => {      
  let users = {
    items: data.user, // data to be updated in db - array of user ids
    item_type: 1,
    id: data.department
  }

  let updateQueries = [];

  // adding query promises to updateQueries
  updateQueries.push(promiseQuery(users.id, users.item_type, users.items));

  RSVP.all(updateQueries).then((results) => {

    /* here 'results' looks like that: 
       [ [ { query: 'INSERT INTO link_to_department (item_type, department, item) VALUES ($item_type, $department, $item)',
             values: [Object] },
          { query: 'DELETE FROM link_to_department WHERE department = $(department) AND item_type = $(item_type) AND item=$(item)',
             values: [Object] } ] ] 

    db update below fails with '[Empty or undefined query.]'*/

    db.tx((trx) => {
        let sqlUpdates = [];

        results.forEach((query) => {
            sqlUpdates.push(trx.none(query.query, query.values))
        })

        return trx.batch(sqlUpdates);
    }).then(() => {
        res.sendStatus(204);
    }).catch((err) => {
        console.log('error', err.message);
        // handle errors
    });
  });
};

2) The function 'promiseQuery' processes data (it compares received data and data in db to update db with the new data):

const promiseQuery = (department_id, item_type, items) => {
    return new RSVP.Promise((resolve, reject) => {
        db.query('SELECT item FROM link_to_department WHERE department=' + department_id + ' AND item_type=' + item_type)
          .then((db_items) => {
            let promises = [];

            let itemsToBeRemoved = [];
            let itemsToBeAdded = [];

            /* here we have array of user ids we received: 'items' 
               and array of user ids from db: 'db_items' */

            // populating 'itemsToBeAdded' and 'itemsToBeRemoved' with user ids that need to added or removed:
            populateUpdateArray(items, db_items, itemsToBeAdded);
            populateUpdateArray(db_items, items, itemsToBeRemoved);

            let insert_query = 'INSERT INTO link_to_department (item_type, department, item) VALUES ($item_type, $department, $item)'
            let delete_query = 'DELETE FROM link_to_department WHERE department = $(department) AND item_type = $(item_type) AND item=$(item)';

            // creating update sql queries
            populateUpdateQuery(insert_query, itemsToBeAdded, department_id, item_type, promises);
            populateUpdateQuery(delete_query, itemsToBeRemoved, department_id, item_type, promises);

            RSVP.all(promises).then((results) => {
               /* here 'results' looks like this:
                  [ { query: 'INSERT INTO link_to_department (item_type, department, item) VALUES ($item_type, $department, $item)',
                      values: { item_type: 19, department: 1, item: '1' } },  
                    { query: 'DELETE FROM link_to_department WHERE department = $(department) AND item_type = $(item_type) AND item=$(item)',
                      values: { item_type: 19, department: 1, item: 1 } }] */

                return resolve(results);
            });

        }).catch(() => {
           reject();
    })
  });
};

3) That function 'populateUpdateArray' populates array of user ids that need to be updated (basically, received user ids should replace ids in the db - for that we check what ids we received are not in db and what ids in db are not in the received ids):

const populateUpdateArray = (array_0, array_1, updateArray) => {
   array_0.forEach((item) => {
      if (array_1.indexOf(item) === -1) {
        updateArray.push(item);
     }
  });
};

4) That function 'populateUpdateQuery' returns sql update queries:

const populateUpdateQuery = (query, id_array, department_id, item_type, promises) => {
   return new RSVP.Promise((resolve, reject) => {
    id_array.forEach((item) => {
        let values = {
            item_type: item_type,
            department: department_id,
            item: item
        };

        promises.push({query, values});
    });

    resolve(promises);      
  });
};

Thank you!

EDIT: I changed the code to have only one db connection and I simplified the code a little. I do not get any errors, but queries are not executed, still. I think I am missing something basic here:

const update = (data) => {
    let users = {
        items: data.user,
        item_type: 1,
        id: data.department
    }

    db.tx((tx) => {
        let updateQueries = [];

        updateQueries.push(promiseQuery(department.id, users.item_type, users.items, tx));

        RSVP.all(updateQueries).then((results) => {
            // results is array of array, so i flatten it
            let sqlUpdates = results.reduce((a, b) => { return a.concat(b); }, []);

            /* sqlUpdates here:   
             [ Promise {
                 _bitField: 0,
                 _fulfillmentHandler0: undefined,
                 _rejectionHandler0: undefined,
                 _promise0: undefined,
                 _receiver0: undefined } ]
            */

            return tx.batch(sqlUpdates);
        });
   }).then(() => {
       res.sendStatus(204);
   }).catch((err) => {
       console.log('error', err.message);
   });
};

const promiseQuery = (department_id, item_type, items, tx) => {
   return new RSVP.Promise((resolve, reject) => {
     tx.query('SELECT item FROM belongs_to_departments WHERE department=' + department_id + ' AND item_type=' + item_type)
        .then((db_items)=> {
            let queries = [];               
            let itemsToBeAdded = [];
            let insert_query = 'INSERT INTO belongs_to_departments (item_type, department, item) VALUES ($(item_type), $(department), $(item))';

            populateUpdateArray(items, db_items, itemsToBeAdded);
            populateUpdateQuery(insert_query, itemsToBeAdded, department_id, item_type, queries, tx);

            resolve(queries);
        }).catch(() => {
            reject();
        });
   });
};

const populateUpdateArray = (array_0, array_1, updateArray) => {
  array_0.forEach((item) => {
     if (array_1.indexOf(item) === -1) {
        updateArray.push(item);
     }
  });
};

const populateUpdateQuery = (query, id_array, department_id, item_type, queries, tx) => {
   id_array.forEach((item) => {
        let values = {
            item_type: item_type,
            department: department_id,
            item: item
        };

        queries.push(tx.none(query, values));
   });
};
sub-zero
  • 66
  • 1
  • 8
  • It is explained in the following common mistake: [Tasks versus root/direct queries](https://github.com/vitaly-t/pg-promise/wiki/Common-Mistakes#tasks-versus-rootdirect-queries) – vitaly-t Nov 28 '16 at 09:43
  • thanks mate, isn't "the right way to do it" from the link exactly what i do in 'update' function with db.tx (for all insert/delete queries)? do you mean i better include 1) select that i have in 'promiseQuery' and 2) all those insert/delete queries into one task/tx? – sub-zero Nov 28 '16 at 10:32
  • It is confusing why your function `populateUpdateQuery` uses promises all over, while it is not even asyncronous... So much is going on to work it out from separate pieces. In general, yes, you should execute all your queries within a single task/transaction. – vitaly-t Nov 28 '16 at 12:36
  • got it, i am going to try it. yes, sorry about that, i was experimenting with promises, i'll remove it now – sub-zero Nov 28 '16 at 12:43
  • @vitaly-t i modified the code to have one transaction only, i get no errors, but sql queries are not executed, still. would be nice to have a comment from you on that if possible. – sub-zero Nov 29 '16 at 11:08
  • Too much code entirely, to be sure, you should simplify your question with just one piece that doesn't work. – vitaly-t Nov 29 '16 at 16:55
  • thanks for your help, mate, the updated query actually works, I just forgot to include variables in the INSERT query into brackets)) – sub-zero Dec 09 '16 at 08:54

1 Answers1

0

Thanks to Vitaly for the help. That worked for me:

const update = data => {
    const users = {
        items: data.user,
        item_type: 1,
        id: data.department
    }

    db.tx(tx => {
        const updateQueries = [];

        updateQueries.push(promiseQuery(department.id, users.item_type, users.items, tx));

        RSVP.all(updateQueries).then(results => {
            // results is array of array, so i flatten it
            const sqlUpdates = results.reduce((a, b) => { return a.concat(b); }, []);                          

            return tx.batch(sqlUpdates);
        });
   }).then(() => {
       res.sendStatus(204);
   }).catch(err => {
       console.log('error', err.message);
   });
};

const promiseQuery = (department_id, item_type, items, tx) => {
   return new RSVP.Promise((resolve, reject) => {
     tx.query('SELECT item FROM belongs_to_departments WHERE department=' + department_id + ' AND item_type=' + item_type)
        .then(db_items => {
            const queries = [];               
            const itemsToBeAdded = [];
            const insert_query = 'INSERT INTO belongs_to_departments (item_type, department, item) VALUES ($(item_type), $(department), $(item))';

            populateUpdateArray(items, db_items, itemsToBeAdded);
            populateUpdateQuery(insert_query, itemsToBeAdded, department_id, item_type, queries, tx);

            resolve(queries);
        }).catch(() => {
            reject();
        });
   });
};

const populateUpdateArray = (array_0, array_1, updateArray) => {
  array_0.forEach((item) => {
     if (array_1.indexOf(item) === -1) {
        updateArray.push(item);
     }
  });
};

const populateUpdateQuery = (query, id_array, department_id, item_type, queries, tx) => {
   id_array.forEach(item => {
        const values = {
            item_type: item_type,
            department: department_id,
            item: item
        };

        queries.push(tx.none(query, values));
   });
};
sub-zero
  • 66
  • 1
  • 8
  • You must never use `RSVP.all` to resolve queries, it won't work well in case of an error. You must only use method `batch` to resolve arrays of queries ;) Have a look at this: https://github.com/vitaly-t/pg-promise/wiki/Common-Mistakes#tasks-versus-rootdirect-queries – vitaly-t Dec 09 '16 at 09:40