Search code examples
node.jspostgresqlknex.js

Javascript function using promises and not returning the correct value


As a relative beginning in Javascript development, I'm trying to understand this problem I'm encountering in a function I've built that will be a part of the code to connect to a postgreSQL database.

In this function, I'm using the knex query builder method to check if a table exists in a remote database, and this method resolves to a boolean that indicates whether the string you specified matches with a table of the same name in the database. I've a provided a sample example of the knex syntax so people better understand the function.

knex.schema.hasTable('users').then(function(exists) {
  if (!exists) {
    return knex.schema.createTable('users', function(t) {
      t.increments('id').primary();
      t.string('first_name', 100);
      t.string('last_name', 100);
      t.text('bio');
    });
  }
});

I am trying to make my function return a boolean by using the .every Array method, which checks each array and should every index pass the condition defined, the .every method should return a true value, otherwise false. I've built a function which takes an array of schema keys, or names of tables, and passes it to the .every method. The .every method then uses the knex.schema.hasTable method to return a true or false.

My concern is that through many different configurations of the function, I have not been able to get it to return a correct value. Not only does it return an incorrect value, which may have something to do with .every, which I believe can return "truthey" values, but after defining the function, I will often get a "Function undefined" error when calling it later in the file. Here is a sample of my function - again I think it is moreso my poor understanding of how returns, promises and closures are working together, but if anyone has insight, it would be much appreciated.

var schemaTables = ['posts','users', 'misc'];

// should return Boolean
function checkTable() {
    schemaTables.every(function(key) {
    return dbInstance.schema.hasTable(key)
        .then(function(exists) {
            return exists;
            });
    });
}

console.log(checkTable(), 'checkTable function outside');
// console.log is returning undefined here, although in other situations,
   I've seen it return true or false incorrectly.

Solution

  • Your function is not working properly for two reasons:

    1. You are not returning the in the checkTable function declaration, so it will always return undefined. You should write:
    function checkTable() {
      return schemaTables.every(function(key) {
        return dbInstance.schema.hasTable(key)
          .then(function(exists) {
            return exists;
          });
      });
    }
    

    Anyway you will not get what you want just adding return. I'll explain why in the second point.

    1. Array.prototype.every is expecting a truthy or falsey value syncronously but what the dbInstance.schema.hasTable returns is a Promise object (and an object, even if empty, is always truthy).

    What you have to do now is checking if the tables exist asynchronously, i'll show you how:

    var Promise = require("bluebird");
    var schemaTables = ['posts', 'users', 'misc'];
    
    function checkTable(tables, callback) {
    
      // I'm mapping every table into a Promise
      asyncTables = tables.map(function(table) {
        return dbInstance.schema.hasTable(table)
          .then(function(exists) {
            if (!exists)
              return Promise.reject("The table does not exists");
            return Promise.resolve("The table exists");
          });
      });
    
     // If all the tables exist, Promise.all return a promise that is fulfilled 
     // when all the items in the array are fulfilled.
     // If any promise in the array rejects, the returned promise 
     // is rejected with the rejection reason.
      Promise.all(asyncTables)
        .then(function(result) {
          // i pass a TRUE value to the callback if all the tables exist, 
          // if not i'm passing FALSE
          callback(result.isFulfilled());
        });
    
    }
    
    
    checkTable(schemaTables, function (result) {
      // here result will be true or false, you can do whatever you want
      // inside the callback with result, but it will be called ASYNCHRONOUSLY
      console.log(result);
    });
    

    Notice that as i said before, you can't have a function that returns a true or false value synchronously, so the only thing you can do is passing a callback to checkTable that will execute as soon as the result is ready (when all the promises fulfill or when one of them rejects). Or you can return Promise.all(asyncTables) and call then on checkTable it self, but i'll leave you this as exercise. For more info about promises check: