Thursday 21 November 2013

Promises In CoffeeScript

Banner: Promises In CoffeeScript

Previously we discussed the benefits of the ‘Comb’ library. We will add to our knowledge here, by discussing ‘Promises’. We are not speaking of promises that are too good to be true! Rather that of a client system willing to fulfill or resolve an operation asynchronously, hence a ‘Promise’. We want to use ‘Promises’, to wrap our code sections, so that we don’t cause blocking in error handling. You will find that your code is also cleaner.

In these examples we will be using CoffeeScript, so we can get a flavor of that emerging language that compiles into JavaScript as well. You will also need to install require.js through the NPM. One tricky thing to remember about CS (CoffeeScript) is that even a tab or a space can cause the compiler to resolve something differently. Here ‘Promise’ has three supported methods that we will cover: errback(), callback(), and resolve().

# initialize the values
fs = require("fs")
comb = require("comb")

# Here we create function to read a file
readFile = (file, encoding) ->
 # Let’s wrap it with a ‘Promise’
 ret = new comb.Promise()
 fs.readFile(file, encoding or "utf8", (err) ->
  if (err)
   # Here is the fail codepath, something was wrong
   ret.errback(err)
  else
   # Success! Resolve the promise
   ret.callback(comb.argsToArray(arguments, 1)))

 # Return the promise object.
 ret.promise()

# Creates an errorHandler function, that writes to a log.
errorHandler = ->
 console.log("Error handler")

How do we use this? The beauty of ‘Promises’, is that you separate the success and fail code paths with a ‘then’ function. See this simple call:

readFile("myFile.txt").then ((text) ->
 console.log(text)
), errorHandler

Now let’s make this even easier: If we use the ‘resolve’ method, we do not have to handle the success and fail code paths separately. Now the ‘callback’ and ‘errback’ methods, are wrapped in a single ‘resolve’ method.

fs = require("fs")
comb = require("comb")


readFile = (file, encoding) ->
 ret = new comb.Promise()
 fs.readFile(file, encoding or "utf8", ret.resolve.bind(ret))
 # Return the promise object.
 ret.promise()

...
...

This version of the ‘readFile’ function does the exact same work as the above, but with a lot cleaner code! Now let’s build on this by adding a ‘listener’. Your purpose, is to listen for the resolution/fulfillment of a ‘promise’. We are looking for the task to be completed, or a failure. Using the ‘readFile’ function from above, the following is listening for a successful: You guessed it, a successful file-read.

readFilePromise = readFile("myFile.txt")

# ‘then’, Remember carries ‘callback’ and ‘errback’, or success and failure.
readFilePromise.then ((file) ->
 console.log("file")
), (err) ->
 console.log(err)

# Here we are ignoring the errback, it is optional.
readFilePromise.then (file) ->
 console.log(file)

Now let’s perform an action on the file after we listen for it to be read. In this sample, let’s convert the contents to uppercase. We can pass the new ‘Promise’ into the ‘Then’ method.

readAndConvertToUppercase = (file, encoding) ->
 ret = new comb.Promise()
 readFile(file).then ((data) ->
  ret.callback(data.toUpperCase())
 ), 
       # This is the errback, but here we can just pass, ‘ret’
       ret
 ret.promise()

readAndConvertToUppercase("myFile.txt").then ((file) ->
 console.log("file")
), (err) ->
 console.log(err)

Enjoy the full sample:

# initialize the values
fs = require("fs")
comb = require("comb")

# read a file
readFile = (file, encoding) ->
 ret = new comb.Promise()
 fs.readFile(file, encoding or "utf8", ret.resolve.bind(ret))
 ret.promise()

readAndConvertToUppercase = (file, encoding) ->
 ret = new comb.Promise()
 readFile(file).then ((data) ->
  ret.callback(data.toUpperCase())
 ), 
    # This is the errback, but here we can just pass, ‘ret’
    ret
 ret.promise()

readAndConvertToUppercase("myFile.txt").then ((file) ->
 console.log("file")
), (err) ->
 console.log(err)