Kodfabrik
RSS
Homepage Blog

Functional Way of Avoiding Nested Callbacks in JavaScript

Functools is a minimal JavaScript library that provides functional programming utilities for manipulating functions and collections both synchronously and asynchronously. I’ve been developing and using it in my almost all projects and want to show you how I avoid nested callbacks using it.

Function composition is the first technique (and my favorite one) that I’ll explain. To give an example for it, assume that we want to find all HTML files in a directory, read their content and send them to a friend via e-mail.

// sendEmail.js

function findHTMLFiles(path, callback){
  implementation++;
}

function readFiles(filenames, callback){
  implementation++;
}

function sendToAFriend(files, callback){
  implementation++;
}

You may notice that the last two functions above take what its previous function produce, with no need of any modification. Which makes them pretty suitable for function composition:

var compose = require('functools').compose;

compose.async(findHTMLFiles, readFiles, sendToAFriend)('/home/me/docs', function(error){
  if(error) throw error;

  console.log('OK :)');
});

Looks much simpler compared to a regular code with 3 nested callbacks. Function composition may also remind you method chanining.

Second technique I would like to mention is juxtaposition. Functools has both sync and async implementations of juxt. Even if you haven’t used it yet on your projects, I think following example will be enough to give the whole idea of it:

function foo(callback){
  setTimeout(callback, 100, 'foo');
}

function bar(callback){
  setTimeout(callback, 250, 'bar');
}

function qux(callback){
  setTimeout(callback, 50, 'qux');
}

So, we have the assume async functions above and need to take all the content of them in a minimalistic way;

var juxt = require('functools').juxt;

juxt.async({ 'foo':foo, 'bar': bar, 'qux': qux)(function(error, results){
  if(error) throw error;

  assert.equal(results.foo, 'foo');
  assert.equal(results.bar, 'bar');
  assert.equal(results.qux, 'qux');
});

Other powerful tools I like using are map, filter and reduce functions. As you expect, both sync and async implementations of them exist in Functools.

To give an example for map and reduce, assume that we have a list of filenames and want to merge the content of them. Here is the implementation using Functools:

var functools = require('functools'),
    map = functools.map,
    reduce = functools.reduce;

var filenames = ['/home/me/docs/foo', '/home/me/docs/bar', '/home/me/docs/qux'];

function readFile(path, callback){
  implementation++;
  callback(undefined, content);
}

function merge(a, b){
  return a + '\n' + b;
}

map.async(readFile, filenames, function(error, contents){

  var all = reduce(merge, contents);
  
  console.log(all); // puts foo\nbar\nqux

});

To summarize, Functools has async implementations of some powerful functional programming tools that can let us avoid nesting callbacks.

There are more examples at the homepage of Functools. Besides of the documentation of it, you may also take a look at Combiner, a command-line tool and library for finding and manipulating files. It’s based on a middleware that lets us initialize different layers of map and filter functions.

It would also be very helpful to check the source code of Functools itself. You’ll notice that it uses map and reduce functions a lot, to implement its remaining functionalities.

Please feel free to share your thoughts, recommandations and examples.

“Just because something is a standard doesn’t mean it is the right choice for every application. Like XML, for example.” — Douglas Crockford, JSConf.eu

Thoughts on IE9 and Teleportation

Imagine a universe that is growing instantly with an environment that includes the necessary resources and basic means of production for its people who live peacefully without borders, governments or militaries. As a result of this, everything is free and all production devices are under control of all humanity, which means a vast increase in production. Additionally, the imagination-universe provides teleportation that makes it easier and faster to distribute/obtain products. Sounds so cool, doesn’t it? I’m talking about the web, we invented a kind of a universe which includes a teleportation network by default! Everyone has access to free production tools (thanks to the FSF movement) and can distribute them to the entire world, for almost free. The only problem that we couldn’t solve is that we have to design our products for old teleportation clients since most of the people are not aware of modern teleportation tools because they prefer to live in private property of a genius Richie Rich.

Even though I’m pretty happy to see the great technical effort on the future of Microsoft Internet Explorer, it’s a disappointment to see that there is still nothing improved about the business approach of Microsoft that seems to believe that they can act like an oil company, posess and control all of the production devices. This is why I’m still not excited about Internet Explorer 9. This may sound like as if I’m a fan of an any other web browser embracing more social business models (BTW My browser choice is UZBL) but I’m actually pretty ok with the technical approach of Microsoft. I’m following IEBlog for years and learning lots of things from the posts about their development experiences, also think that MSDN (Compare it with Apple’s mysterious reference pages) is one of the great resources for web developers. The only thing to which I oppose is business models of Microsoft products.

On the other hand, Internet Explorer isn’t the only proprietary browser which belongs to a commercial company, we have Safari and several mobile web browsers, too. The only difference between the IPhone’s mobile browser and Internet Explorer 6 is that IPhone ones rendering engine is a little better, not much. Distribution methods -even though Webkit is open source, it’s not perfect)- are almost same, except the companies are different. Most of the mobile browsers are IE6 candidates to me.

To summarize, the main issue about the web is whether to take the advantage of modern distribution methods or not. Even if all of the web browsers follow the web standards, we will continue to lose time for the software playing catch-up since implementation times differ between months and years. In my opinion, all of the rendering/javascript engines should be open source, able to update itself automatically and totally independently from browsers. This is what the web needs immediately.

Many thanks to Yusuf Arslan for reading draft versions of this.

vim: tw=100

“I have been writing JavaScript for 8 years now, and I have never once found need to use an uber function. The super idea is fairly important in the classical pattern, but it appears to be unnecessary in the prototypal and functional patterns. I now see my early attempts to support the classical model in JavaScript as a mistake.” — Douglas Crockford

“It’s better to have 100 functions operate on 1 data structure than to have 10 functions operate on 10 data structures” — Alan Perlis

Volume Widget For AwesomeWM 3.4

Ubuntu 9.10 was the distro I had been using for 10 months with AwesomeWM until my Arch Linux migration on last week. I started the migration by moving my development environment to the VPS located in Linode servers firstly, configuration of the hardware and desktop environment followed this step. During the configuration I noticed that most of the widgets in the AwesomeWM wiki doesn’t work with latest release. I took this situation as a sign  from the Gods of computer world to start learning Lua and read Programming In Lua book which is available to read online for free. A little training that took just 3-4 hours made me available to code a volume control widget for AwesomeWM. Here is the code:

------------------------------
-- Volume Widget For AwesomeWM 3.4
-- Azer Koculu <azerkoculu@gmail.com>
-- Wed Apr 7 01:34:35 UTC 2010
------------------------------
local widgetobj = widget({ type = 'textbox', name = 'volume_widget' })
local channel = "vmix0-outvol"

local function increase()
  awful.util.spawn("ossvol -i 3")
  update()
end

local function decrease()
  awful.util.spawn("ossvol -d 3")
  update()
end

local function update()
  local fd = io.popen("ossmix " .. channel)
  widgetobj.text = 'VOL' .. fd:read("*all"):match("(%d+%.%d+)")
  fd:close()
end

local function mute()
  awful.util.spawn("ossvol -t")
end

widgetobj:buttons(awful.util.table.join(
  awful.button({ }, '4', increase),
  awful.button({ }, '5', decrease),
  awful.button({ }, '1', mute)
))

--[[
globalkeys = awful.util.table.join(globalkeys,
awful.key({ }, "XF86AudioRaiseVolume", increase,
awful.key({ }, "XF86AudioLowerVolume", decrease,
awful.key({ }, "XF86AudioMute", mute)
)
]]--

local utimer = timer({ timeout=1 })
utimer:add_signal("timeout", update)
utimer:start()

update()

volume = {
  channel = channel,
  widget = widgetobj,
  increase = increase,
  decrease = decrease,
  mute = mute
}

return volume

I also coded an unnecessary wallpaper package without checking out man page of awsetbg, which provides randomizing already.

----------------------
-- Random Wallpaper Package For Awesome WM
-- Azer Koculu <azerkoculu@gmail.com>
--
-- EXAMPLE USAGE
-- ------------
-- require "wallpaper"
-- theme.wallpaper_cmd = "awsetbg " .. wallpaper.pick( wallpaper.collect { "/home/foo/pic/wal1", "/home/foo/pic/wal2" } )
--
----------------------
local image_extensions = { jpg=true, png=true }

-- test whether given filename is an image
local function is_image(filename)
  return image_extensions[filename:match("%.(%w+)$")]
end

-- gather images from the passed directories
local function collect(dirs)
  local images = {}
  for i=1,table.getn(dirs),1 do
    local dir = dirs[i]
    for file in io.popen("ls "..dir):lines() do
      if is_image(file) then
        table.insert(images,dir .."/".. file)
      end
    end
  end
  return images
end

-- pick a random item from given table
local function pick(t)
  n = table.getn(t)
  if n > 0 then
    math.randomseed(os.time()+n)
    el = t[math.random( n )]
  end
  return el
end

-- declare exports
wallpaper = {
  extensions=image_extensions,
  collect=collect,
  pick=pick
}

return wallpaper

Lispy Fluent Interfaces In Javascript

By inspiring from Lisp and the functional programming utilities came with Javascript 1.6, I’ve coded a new function to iterate arrays -especially for those containing DOM nodes- by providing an alternative fluent interface and chaining. Usage examples;

// log elements of an array
each( ['Hello','World'] )
  (console.log)

// disable all form elements passing additional arguments
each( document.querySelectorAll('input, select, textarea') )
  (setattr, 'disabled', true)

// apply header elements several dom manipulations
each(document.querySelectorAll('header'))
  (style, 'fontSize', '16px Arial,sans-serif')
  (style, 'background', '#ffff00')
  (style, 'padding', '3px')
  (add_class, 'Foobar')
  (add_event, 'click', function(){ alert('Hello World') })

And here is the source code:

/**
 * A Function Providing Lispy Iteration For Javascript
 * @author Azer Koculu <azerkoculu@gmail.com>
 */
var each = function(list)
{
  var caller = function(fninitial)
  {
    var cargs = Array.prototype.slice.call( arguments, 1);
    
    var func = function(el)
    {
      var args = [ el ];
      Array.prototype.push.apply(args,cargs);
      fninitial.apply(null, args);
    }

    Array.prototype.forEach.call( list, func );

    return caller;
  }
  return caller;
}

The function defined in the code above simply returns a function returning itself and taking a function with optional arguments to call it by passing the element being iterated and the optional arguments specified. Thus, the high-order-function I’ve pointed make the iteration chainable, as well.

P.S: Functional module of the new web framework I’ve been working on provides some similar tools with much better implementation, which are available to be checked out;

  • Source Code
  • Tests

Tags

alan perlis arch async-programming awesomewm callbacks data structures douglas-crockford ecmascript fluent interfaces function-composition functional functional-programming functools internet-explorer iphone javascript jsconf-eu juxtaposition linode lisp lua microsoft mobile-safari nodejs oop quotes rendering-engines safari standards teleportation ubuntu web-browsers xml
Last Update: 2012-02-14 14:59:54 -0800 - Generated By Jekyll / Source Code
★ 2006-2012 Kodfabrik
gipoco.com is neither affiliated with the authors of this page nor responsible for its contents. This is a safe-cache copy of the original web site.