`Bind` with named methods goes a long way to cleaning up nested callbacks. Here's a (quick+dirty; I know I'm missing some `do`'s in there) version of the same code in CoffeeScript:
_ =
fileMenu: ->
mainWindow.menu "File", (e,f) => this.onFile(e,f)
onFile: (err,file) ->
if err then throw err
file.openMenu (e,m) => this.onOpenMenu(e,m)
onOpenMenu: (err,menu) ->
if err then throw err
menu.item "Open", (e,i) => this.onOpen(e,i)
onOpen: (err,item) ->
if err then throw err
mainWindow.getChild type('Window'), (e,d) => this.onDialog(e,d)
onDialog: (err,dialog) ->
if err then throw err
#...
_.fileMenu()
( `=>` is CoffeeScript for a function bound to the current scope )
We've adopted a way that allows us to do basically this:
mainWindow.menu "File", (err, file)->
openMenu err, (err, menu)->
getItem err, "Open", (err, item)->
click err, item, (err)->
getChild err, getChildType("Window"), (err, dialog)->
if err? and err instanceof NoFileError
# do stuff
if err? and err instanceof InvalidClickError
# do other stuff
if err?
throw err
# do stuff with dialog if no err
Each function takes as first argument an error and as last a callback. If there's
an error, the function just passes the error up the callback stack until it gets
dealed with.
fun = (err, args..., callback)
You can't pull that off if your code is heavily object oriented though:
mainWindow.menu "File", (err, file)->
# If mainWindow.menu errors, file is undefined
# and the call to openMenu throws an error
file.openMenu err, (err, menu)->
# ...
Breaking your code/api up with modules instead into classes fixes that.