Bert Belder


aka @piscisaureus


node and libuv and strongloop

"Future of asynchronous programming in node"

"The future of node streams. We need to talk"

"Callbacks as our Generations' Go To Statement"

node 2

  • Double / missed callbacks
  • Ad-hoc ironing out of flow control kinks
  • Not handling errors (effectively)
  • To nextTick or not nextTick
              
var waiting = 2;

stream1.on('close', function(err) {
  if (--waiting === 0)
    nextStep();
});

process.on('exit', function() {
  if (--waiting === 0)
    nextStep();
});
              
            
                
var Url = require('url'),
    Http = require('http'),
    Fs = require('fs');


function download(url, filename, cb) {
  var urlObject = Url.parse(url),
      file;

  var request = Http.get(urlObject, function(res) {
    if (res.statusCode != 200) {
      makeCallback(new Error("HTTP Error: " + res.statusCode));
      return;
    }

    file = Fs.createWriteStream(filename);
    res.pipe(file);

    file.on('error', function() {
      file.destroy();
    });

    file.on('close', function() {
      file = null;
      makeCallback();
    });
  });

  request.on('error', function(err) {
    if (file) file.destroy();
    makeCallback(err);
  });

  function makeCallback(err) {
    if (!cb) return;
    cb(err);
    cb = null;
  }
}
              
            

Callbacks as our Generations' Go To Statement

  • "construct" for working with callbacks
  • express intent over inner workings
  • build robust applications
  • un-opinionated
  • doesn't break existing apps/modules
  • in core!
            
try_async(callback) {
  // Do async stuff.
  // Eventually:
  succeed_with 42;

  // Or you might:
  throw new Error('Вы облажался');

} catch_finally_async(err, result) {
  // We get here when all async stuff
  // in the try_async block is over.
}
            
          
            
task.create(function(callback) {
  // Do async stuff.
  // Eventually:
  callback(null, 42);

  // Or you might:
  throw new Error('Вы облажался');

}).setCallback(function(err, result) {
  // We get here when all async stuff
  // in the try_async block is over.
});
            
          
            
task.create(function(callback) {
  // Within the outer task.

  task.create(function(callback) {
    // Within a nested task.

  }).setCallback(err, result) {
    // Back in the outer task
    if (err) throw err;
    callback(null, result + 1);
  });

  setTimeout(function() {
    // In the outer task too.
    throw new Error("It's too slow");
  }, 100);

}).setCallback(function(err, result) {
  // In the global task.
});
            
          

  • fs.readFile('/foo/bar', cb)
  • 100 lines of code
            
fs.readFile = function(path, options, callback_) {
  ...

  function afterRead(er, bytesRead) {
    if (er) {
      return fs.close(fd, function(er2) {
        return callback(er);
      });
    }

    ...
  }

  ...
}
            
          
            
fs.readFile = function(path, options, callback_) {
  return task.create(function(callback) {
    fs.open(path, function(err, fd) {
      if (err) throw err;

      var result = '', offset = 0;
      doRead();

      function doRead() {
        fs.read(fd, afterRead, offset, 65536);
      }

      function afterRead(err, data) {
        if (err)
          throw err;
        if (!data.length) // EOF
          return fs.close(fd, afterClose);

        offset += data.length;
        result += data.toString();
        doRead();
      }

      function afterClose(err) {
        if (err)
          throw err;
        callback(null, result);
      }
    });
  }).setCallback(callback_);
};
            
          

EventEmitter

            
task.create(function(callback) {
  var conn = net.createConnection(80, 'www.google.com');

  conn.on('data', function(data) {
    callback(null, data);
  });
});
            
          
            
var conn = net.createConnection(80, 'www.google.com');

task.create(function(callback) {
  conn.on('data', function(data) {
    callback(null, data);
  });
});
            
          

EventEmitter -> Resource

  • All tasks that have a listener on a resource event are 'vulnerable'
  • You can have an error handler within a task.

            
var conn = net.createConnection(80, 'www.google.com');

task.create(function(callback) {
  conn.on('data', function(data) {
    callback(null, data);
  });

  conn.on('error', function(err) {
    // You'd only need to do this if you actually wanted to
    // handle this - because default action is implied:
    throw err;
  });
});
            
          
  • Listeners are 'ended' when the Resource is closed
  • No external .emit()

First tick
setTimeout cb
accept cb
read cb
read cb
write cb
shutdown cb
accept cb
read cb
write cb
shutdown cb

First tick
setTimeout cb
accept cb
read cb
read cb
write cb
shutdown cb
accept cb
read cb
write cb
shutdown cb

            
task.create(function OuterTask(callback) {

  task.create(function InnerTask(callback) {
    // beep
  }).setCallback(err, result) {
    // boop
 });

  setTimeout(function() {
    throw new Error("It's too slow");
  }, 100);

}).setCallback(function(err, result) {
  throw err;
});
            
          
Error: ugly, yellow, no-good keister off my property

Task: InnerTask (lib/server.js:52:3)
    at <anonymous> (lib/index.js:15)
    at invokeTimer (timer.js:110:21)
    at TimerWrap.ontimeout

Task: OuterTask (lib/index.js:1:13)
    at Task.<anonymous> (as _complete) (index.js:14:3)
          

Hello nu.js


http://github.com/nujs/nu
http://nujs.github.io/nodeland-2013

Bikeshedding workshop!