javascript turning my authentication function into a promise

Here's an example of my access function that I use to check whether or not the user is authenticated.

access = function(id, user_id, callback) {
  docs.findOne({
    _id: id
  }, function(err, doc) {
    if (doc.user.indexOf(user_id) != -1) {
      callback("authenticated")
    } else {
      callback();
    }
  });
}

I use it as with the callback pattern like this.

access(id, user, function (status) {
  if (status == 'authenticated') doSomething()
})

I'm finding that somewhat boring to write as you really need to check the callback status every time you use the access function. I would like a better way to use the access function so that authentication is assumed on the callback. Something like this?

access(id, user, function () {
  doSomething()
}).err(function () {
  doSomethingElse()
})

If you want a half hour introduction to promises in video form I'd suggest my talk from JSConf.eu: http://youtu.be/qbKWsbJ76-s

I get the impression from your question that you haven't really looked at promises before (and from some of the other answers that not everyone else has either).

JavaScript promises are defined in the Promises/A+ specification. It's very easy to read so worth a look. Not all promises follow this spec (jQuery being the only really noteworthy exception) but you can find a list of compliant implementations here

As a newcomer:

If you want to see how promises are implemented, promise is one of the simplest (it also has among the fastest performance). DISCLAIMER: I wrote this one

Promise is extremely limited though, so without lots of extensions it's not of much use. I'd recommend you use Q to start with as it's one of the most popular and has lots of features that make using it much simpler. Q also fits best with most of the thinking in future specifications.

If you are using Mongoose as your database driver it comes with its own implementation of Promises/A+ so you could just use that:

access = function(id, user_id) {
  return docs.findOne({
    _id: id
  })
  .then(function(doc) {
    if (doc.user.indexOf(user_id) == -1) {
      throw new Error('User Not Found')
    }
  });
}

What's going on here is that docs.findOne is returning a "promise" because it was called without a callback. The handler attached by .then adds a callback. It also returns a new promise and any errors from the first promise are automatically propagated to the new promise. If an error is thrown inside the callback that new promise is "rejected". If the callback returns a value then the new promise is "fulfilled" with that value.

Having defined our function like that we can call it:

access(id, user_id)
  .then(function () {
    doSomething();
  });

If at some point we want to handle that error, we can do so by attaching an error handler:

access(id, user)
  .then(function () {
    doSomething()
  }, function (err) {
    doSomethingElse()
  })

The only issue is that if doSomething throws an error it will get silenced (which is bad). If you are done chaining .then calls onto a promise you need to end that chain.

If you are using the promises built into mongoose then you call end

access(id, user)
  .then(function () {
    doSomething()
  }, function (err) {
    doSomethingElse()
  })
  .end();

If you were using Q promises then you would call done

access(id, user)
  .then(function () {
    doSomething()
  }, function (err) {
    doSomethingElse()
  })
  .done();

If you have a mongoose promise that you want to convert to a Q promise you can simply pass it to Q:

Q(access(id, user))
  .then(function () {
    doSomething()
  }, function (err) {
    doSomethingElse()
  })
  .done();

If you're not using Mongoose

If you're not using Mongoose and you need to create a Q promise from scratch you use Q.promise

var promise = Q.promise(function (resolve, reject) {
  docs.findOne({
    _id: id
  }, function(err, doc) {
    if (doc.user.indexOf(user_id) != -1) {
      resolve("authenticated")
    } else {
      reject(new Error("User Not Found"));
    }
  });
})

There's also a shortcut for handling node style APIs:

var findOne = Q.denodeify(docs.findOne);
var promise = findOne({_id: id});

Design Reasonning

Finally, if you're wondering why promises work the way they do or you think their design is silly in some way, https://github.com/kriskowal/q/blob/master/design/README.js is an excellent guide. It takes you through the development of a promise library from the ground up, starting pretty much exactly where you are with thinking "callbacks aren't quite good enough" all the way up to a full promise library.

lets split that:

access(id, user, function () {
  doSomething()
}).err(function () {
  doSomethingElse()
});

means the same as

var ret = access(id, user, function () {
  doSomething()
});

ret.err(function () {
  doSomethingElse()
})

so, err(...) would be called all the time - not what you're intending.

what about

var Access = function ()
{
   this.errorHandler = null;

   this.registerErrorHandler = function (errh)
   {
      this.errorHandler = errh;

...
   this.authenticate = function (...)
   {



       // if not authenticated
       this.errorHandler(....)

Just as an idea? this.errorHandler could also be an array of handlers, etc.

In order to call that you need to do:

myAccess = new Access();
myAccess.registerErrorHandler(xyz);
myAccess.authenticate(.....);

I believe Mongoose returns promises for all its query functions, so something like the following would work, I think:

var access = function(id, user_id, callback) {
  return docs.findOne({_id: id}).then(function (doc) {
    if (doc.user.indexOf(user_id) != -1) throw(new Error("Wrong user_id."))
  }) // not handling any errors here
}

You can then use it like so:

access(id, user).then(function () {
  doSomething()
}, function (error) { // this "onRejected" handler is called when findOne fails, 
  console.log(error) // or if user_id doesn't match 
  doSomethingElse() // maybe decide what to do based on the type of error?
})

If you're not using Mongoose, but the "native" MongoDB client, then you need to create a promise yourself first. There are a lot of libraries to do so. I recommend to look for a Promises/A+ compliant library. Most are easy to recognize because their homepages (or readme's) prominently feature a yellow square with "then" written in it.