开发者

Flow control in JavaScript

Is it possible to write this flow control in JavaScript?

MyLib.get = function() { /* do something */ next(); };
MyLib.save = function() { /* do something */ next(); };
MyLib.alert = function() { /* do something */ next(); };

MyLib.flow([
  MyLib.get(),
  MyLib.save(),
  MyLib.alert()
], 开发者_C百科function() {
  // all functions were executed
});


Yes, but two things to know:

  1. You should build the array from references to your functions. That means you leave off the (), because you just want to pass through the reference, not the result of calling the function!
  2. You're going to have to deal with the fact that getting references to functions from properties of an object will not "remember" the relationship to that object. Thus, the "flow" code would have no way to know how to invoke the functions with "MyLib" as the this context reference. If that's important, you'll want to create functions that run your "member" functions in the right context.

To run the functions in the right context, you can cobble together something like the "bind" function supplied by the Prototype framework (and also, among many others, Functional.js), or $.proxy() from jQuery. It's not that hard and would probably look like this:

function bindToObject(obj, func) {
  return function() {
    func.apply(obj, arguments);
  }
}

then you'd use it like this:

MyLib.flow([
  bindToObject(MyLib, MyLib.get),
  bindToObject(MyLib, MyLib.save),
  bindToObject(MyLib, MyLib.alert)
]);

If you need for parameters to be passed in, you could modify "bindToObject":

function bindToObject(obj, func) {
  var preSuppliedArgs = Array.prototype.slice.call(arguments, 2);
  return function() {
    func.apply(obj, preSuppliedArgs.splice(arguments));
  }
}

That assumes you'd want additional arguments passed when the "bound" function is called to be tacked on to the end of the argument list. In your case, I doubt you'd want to do that, so you could leave off that "splice()" call.


This looks like continuation-passing style. However, normally in that style, each function takes a next function as an argument, like this:

MyLib.get = function(next) { /* do something */ next(); };
MyLib.save = function(next) { /* do something */ next(); };
MyLib.alert = function(next) { /* do something */ next(); };

and as Pointy notes, you would normally pass the functions themselves, without having already called them:

MyLib.flow([
  MyLib.get,
  MyLib.save,
  MyLib.alert
], function() {
  // all functions were executed
});

With those changes, the code below might work.

Now, it's by no means obvious to the uninitiated exactly how continuation-passing style works just by looking at the code. I don't think I can make it clear in a single answer. But I'll try.

In this style, even a do-nothing function would not be totally empty, but would have to call next:

MyLib.do_nothing = function(next) { /* don't do something */ next(); };

One important building block is the ability to take two functions written in this style and chain them together:

// This function takes two CPS functions, f1 and f2, as arguments.
// It returns a single CPS function that calls f1, then f2, then next().
MyLib._compose2 = function (f1, f2) {
    return function(next) {
        return f1(function () { return f2(next); });
    };
};

This is the hardest bit to understand, I think.

Once you have that, you can use it to glue together any number of functions:

MyLib._composeAll = function (arr) {       // Easy!
    var result = do_nothing;               // Start with the "empty" function,
    for (var i = 0; i < arr.length; i++)   // and one by one,
        result = MyLib._compose2(result, arr[i]);  // add each element of arr.
    return result;
};

And once you have that, flow is not too hard to write:

MyLib.flow = function(items, next) {
    var f = MyLib._composeAll(items);
    f(next);
};

Fixing all the bugs in that code is left as an exercise. ;)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜