Concept: making asynchronous loading look synchronous

by
, posted

Note: this post is outdated; try async and await.

Callback Hell is a real place, for better or worse. Asynchronous programming is sweet, though; it’s not the asynchronicity that bothers me, but the syntax.

I wanted to solve the ugliness of asynchronous syntax in browser-based script loaders like RequireJS and LABjs. We can barely solve it. And only in Firefox. Please be kind.

The basic idea of how this works requires a Firefox-only property called __noSuchMethod__. It works like this:

var myObject = {
  __noSuchMethod__: function (id, args) {
    console.log(
      "A method called " +
        id +
        " was called with the following arguments:"
    );
    console.log(args);
  },
};

myObject.someMethod(1, 2, 3);
// "A method called someMethod was called with the following arguments:"
// [1, 2, 3]

Okay, so shelf that away for a minute.

Let’s say I’ve written a dead simple logging library. It looks like this:

var logger = {
  log: function (message) {
    console.log(message);
  },
  version: function () {
    return "1.2";
  },
};

So logger has two methods: log and version. Not too crazy.

Now, let’s say we want to use this library. Here’s the dreamcode that we want:

var logger = require("logger.js");
logger.log("Hello world!");

var version = logger.version();
logger.log(version);

Having written a simple asynchronous script loader myself, they basically work by injecting a <script> tag, and when that loads, they run a callback.

So here’s how this works: require is going to immediately return an object that has one method no it: __noSuchMethod__. It’ll build up a queue of method calls, and once the script loads, it’ll execute all the methods in that queue.

Here’s what that looks like in imperfect code:

// A lil' function that requires stuff.
var require = function (src) {
  // Define a queue of methods which we'll run when we get things loaded.
  var methodQueue = [];

  // Load the script.
  var script = document.createElement("script");
  script.src = src;
  document.head.appendChild(script);

  // When the script loads, run everything in the method queue.
  script.onload = function () {
    var exported = logger; // TODO: doesn't work for other libraries!
    methodQueue.forEach(function (method) {
      exported[method.id].apply(exported, method.args);
    });
  };

  // Return a dummy object which will throw methods into the queue.
  return {
    __noSuchMethod__: function (id, args) {
      methodQueue.push({
        id: id,
        args: args,
      });
    },
  };
};

We’d have to figure out some way of getting exported programmatically inside of the callback, probably with some overhead in the library like RequireJS needs. But this is the basic idea – return an object that defers method calls.

When we run the demo in Firefox (you’ll want to view the source of that page), it sorta works. For the simplest of things – a simple method call – it works. But if a return value is needed, it doesn’t work.

This is just a shoddy concept and it might be impossible, but I thought it was fun to explore!