1

When I get a request, I want it to generate a 4-character code, then check if it already exists in the database. If it does, then generate a new code. If not, add it and move on. This is what I have so far:

var code = "";
var codeFree = false;
while (! codeFree) {
    var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    var code = "";
    for (var i = 0; i < 4; i++) {
        var rand = Math.floor(Math.random() * chars.length);
        console.log(rand);
        code += chars.charAt(rand);
    }
    console.log("Code: %s generated.", code);

    client.execute("select * from codes where code=" + code, function(err, result) {
        if (! err) {
            if (result.rows.length > 0) {
                codeFree = false;
            } else {
                codeFree = true;
            }
        } else {
            console.log('DB ERR: %s', err);
        }
        console.log(codeFree);
    });
    console.log('here');
}

This does not do nearly what I want it to do. How can I handle something like this?

2 Answers 2

2

You are doing an async task.

When you have an asyncronous task inside your procedure, you need to have a callback function which is going to be called with the desired value as its argument.

When you found the free code, you call the function and passing the code as its argument, otherwise, you call the getFreeCode function again and passing the same callback to it. Although you might consider cases when an error happens. If your the db call fails, your callback would never get called. It is better to use a throw/catch mechanism or passing another argument for error to your callback.

You can achieve what you need to do by doing it this way:

function getFreeCode(callback) {
    var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    var code = "";
    for (var i = 0; i < 4; i++) {
      var rand = Math.floor(Math.random() * chars.length);
      console.log(rand);
      code += chars.charAt(rand);
    }
    console.log("Code: %s generated.", code);

    client.execute("select * from codes where code="+code, function(err, result) {
      if(!err) {
        if(result.rows.length > 0) {
          getFreeCode(callback);
        } else {
          callback(code);
        }
      }else {
        console.log('DB ERR: %s', err);
      }
      console.log(codeFree);
    });
    console.log('here');
}




// in your main:

getFreeCode(function (code) {
    console.log(' this code was free: ' + code)
})
Sign up to request clarification or add additional context in comments.

5 Comments

Now, is there any way to achieve something like var newCode = getFreeCode(); ??
Not that easily. When you have async tasks, at some points you have to have a function running out of order. You can use Promise pattern though. If you use ES6, you can achieve more readable code using yield and generator functions when dealing with async functions. Look at this module. and search in google for js promise and js promise yield generator function.
Now, say in theory that before getFreeCode returns it makes it so that the code is no longer free, is it still possible that the same code be returned free because it is running asynchronously on the server?
once getFreeCode finds a free code, it passes the value to you. In the meantime that you are processing it and doing other db calls, another request could come and ask for a free code, getFreeCode could randomly generate the same code for him as well. The probability is very low though if you use longer random codes.
If you need more info on this, you can open another question to discuss. If this already answers your question, please consider upvoting and marking it as the answer for future visitors.
2

I recommend you look into two alternatives to help deal with asynchronous code.

  1. node generator functions using the 'yield' keyword
  2. promises

Using generators requires running a recent version of node with the --harmony flag. The reason I recommend generators is because you can write code that flows the way you expect.

var x = yield asyncFunction();
console.log('x = ' + x);

The previous code will get the value of x before logging x.

Without yielding the console.log would write out x before the async function was finished getting the value for x.

Your code could look like this with generators:

var client = {
    execute: function (query) {
        var timesRan = 0;
        var result = [];
        return function () {
            return setTimeout(function () {
                result = ++timesRan < 4 ? ['length_will_be_1'] : [];
                return result;
            },1);
        };
    }
};

function* checkCode () {
    var code;
    var codeFree = false;
    while(!codeFree) {
    var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    code = "";
    for (var i = 0; i < 4; i++) {
      var rand = Math.floor(Math.random() * chars.length);
      console.log(rand);
      code += chars.charAt(rand);
    }
    console.log("Code: %s generated.", code);

    try {
        var result = yield client.execute("select * from codes where code="+code);
      codeFree = result.rows.length > 0 ? false : true;
      }catch(e) {
        console.log('DB ERR: %s', err);
      } finally {
        console.log(codeFree);
      }

    console.log('here');
  }
}

checkCode().next();

You would leave off the client object. I only added that to make a working example that fakes an async call.

If you have to use an older version of node or do not like the yield syntax then promises could be a worthy option.

There are many promise libraries. The reason I recommend promises is that you can write code that flows the way you expect:

asyncGetX()
.then(function (x) {
    console.log('x: ' + x);
});

The previous code will get the value of x before logging x.

It also lets you chain async functions and runs them in order:

asyncFunction1()
.then(function (result) {
    return asyncFunction2(result)
})
.then(function (x) { /* <-- x is the return value from asyncFunction2 which used the result value of asyncFunction1 */
    console.log('x: ' + x);
});

Your code could look like this with the 'q' promise library:

var Q = require('q');

var client = {
    timesRan: 0,
    execute: function (query, callback) {
        var self = this;
        var result = {};
        setTimeout(function () {
            console.log('self.timesRan: ' + self.timesRan);
                result.rows = ++self.timesRan < 4 ? ['length = 1'] : [];
                callback(null, result);
        },1);
    }
};

function checkCode () {
    var deferred = Q.defer();
    var codeFree = false;
  var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  var code = "";
  for (var i = 0; i < 4; i++) {
    var rand = Math.floor(Math.random() * chars.length);
    console.log('rand: %s', rand);
    code += chars.charAt(rand);
  }
  console.log("Code: %s generated.", code);

    client.execute("select * from codes where code="+code, function(err, result) {
        console.log('err: '+err+', result: ' + JSON.stringify(result));
        console.log('result.rows.length: ' + result.rows.length);
    if(!err) {
      if(result.rows.length > 0) {
        codeFree = false;
        console.log('result.rows: %s, codeFree: %s', result.rows, codeFree);
        checkCode();
      } else {
        codeFree = true;
        console.log('line 36: codeFree: ' + codeFree);
        deferred.resolve(code);
      }
    }else {
      console.log('DB ERR: %s', err);
          deferred.reject(err);
    }
    console.log(codeFree);
  });
  console.log('waiting for promise');

  return deferred.promise;
}

checkCode()
.then(function (code) {
    console.log('success with code: ' + code);
})
.fail(function(err) {
  console.log('failure, err: ' + err);
});

Also omit the client object here. I only added that to make a working example that fakes an async call.

Promises and generators definitely take some time to get used to. It's worth it because they make the code a lot easier to follow in the end than code written with nested callbacks.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.