github
Advanced Search
  • Home
  • Pricing and Signup
  • Explore GitHub
  • Blog
  • Login

mbrubeck / oni-map

  • Admin
  • Watch Unwatch
  • Fork
  • Your Fork
  • Pull Request
  • Download Source
    • 1
    • 0
  • Source
  • Commits
  • Network (0)
  • Issues (0)
  • Downloads (0)
  • Wiki (1)
  • Graphs
  • Branch: master

click here to add a description

click here to add a homepage

  • Branches (1)
    • master ✓
  • Tags (0)
Sending Request…
Enable Donations

Pledgie Donations

Once activated, we'll place the following badge in your repository's detail box:
Pledgie_example
This service is courtesy of Pledgie.

An experiment in async/parallel array functions for JavaScript — Read more

  cancel

  cancel
  • Private
  • Read-Only
  • HTTP Read-Only

This URL has Read+Write access

Rewrite EachWith/MapWith as partially curried functions. 
Matt Brubeck (author)
Mon Mar 09 08:56:09 -0700 2009
commit  97e8dd24c0bd7b265428c463ebd98e274ed73cf9
tree    8331f41b9bdffc9732a59a9fab8dad8717d39cd2
parent  ed11eab6fa260dc77ec3e2201fccb6c6e661647e
oni-map /
name age
history
message
file README.markdown Loading commit data...
file async-map.js
directory oni/
file test.html
README.markdown

Asynchronous Array Functions in JavaScript and Oni

When writing JavaScript for web browsers, you have to deal with asynchronous functions a lot. Because the environment is single-threaded and event-driven, everything from DOM events to the HTML5 Database API requires the programmer to pass around callbacks and continuations. This is especially important on some handheld systems like Google Android, where the browser won't redraw the screen until the programmer returns control to the main event loop.

Each, Map, Fold

I most recently ran into this when writing an automated test-runner for our JavaScript application at work. Since our tests interact with the DOM, they need to run asynchronously and use callbacks to report their results. If I launched them in a for loop, they'd all run in parallel and interfere with each other.

So I wrote a generic method to loop over an array asynchronously:

/**
 * Call f on each element of items, then call done_callback.
 * f should have signature f(item, next), and should call next() when it is done.
 */
each_seq = function(items, f, done_callback) {
  var iter = function(i) {
    if (i < items.length) {
      var item = items[i];
      f(item, function() { iter(i+1); });
    } else if (done_callback) {
      done_callback();
    }
  };
  iter(0);
};

With that handy little function, I can easily run my tests in sequence:

each_seq([test1, test2, test3],
  function each(test, next) {
    test.run(function callback() {
      console.log("test completed: " + test.status);
      next();
    });
  },
  function done() { console.log("all done!"); });

Just for fun, I decided to get even more generic and write an asynchronous fold:

var foldl_seq = function(step, zero, arr, callback) {
  var next = function(i, acc) {
    if (i < arr.length) {
      step(acc, arr[i], function(result) { next(i+1, result); });
    } else {
      callback(acc);
    }
  };
  next(0, zero);
};

Then I could implement map and each in terms of fold:

var map_seq = function(f, arr, callback) {
  foldl_seq(function(a, b, next) { 
      f(b, function(result) { next(a.concat([result])); });
    }, [], arr, callback);
};

var each_seq = function(f, arr, callback) {
  foldl_seq(function(a, b, next) { f(b, next); }, null, arr, callback);
};

Map and Each in Oni

Next, I wanted to try the same thing in the very cool Oni concurrency library. Oni is a functional language embedded in JavaScript (and potentially in other host languages) that hides the complexity of flow control for asynchronous code. For details, check out the excellent documentation and slides on the Oni site.

One nice thing about Oni is that choosing parallel or sequential execution is simple and declarative. I didn't use Fold this time because it doesn't parallelize. Writing Each was easy, but Map was a little more complicated:

/**
 * EachWith(Par, F, [a,b,...]) -> Par(F(a), F(b), ...)
 * EachWith(Seq, F, [a,b,...]) -> Seq(F(a), F(b), ...)
 */
var EachWith = function(Combinator, F, arr) {
  return Combinator.apply(this, arr.map(F));
};

/**
 * MapWith(Combinator, F, [a,b,...]).run() -> [<F(a)>, <F(b)>, ...]
 *
 * "Combinator" can be Par (to call F in parallel) or Seq (to call F sequentially).
 */
var MapWith = function(Combinator, F, arr) {
  return Let({ result: [], arr: arr },
    Seq(
      EachWith(Combinator,
        Lambda(['i'], SetAt('result', 'i', F(GetAt('arr', 'i')))),
        range(arr.length)),
      Get('result')));
};

Writing them in Oni was a bit more work, but now they can be combined with Oni's other combinators to work with both synchronous and asynchronous functions, in parallel and in sequence. You can even call them with Alt if you don't care about getting all the results back. They're tricky enough that it would be nice to have version of them in the standard Oni library. (A version of Apply that takes its arguments as a JavaScript array would make the implementation more obvious, too.)

Code

See async-map.js for the full source to both the pure JavaScript and Oni-based functions. Open test.html in your browser with a debugging console (e.g. Firebug) enabled to run the included tests.

My EachWith uses Array.prototype.map from JavaScript 1.6. You could provide a map implementation for browsers that don't have their own. See the source file for the definitions of GetAt, SetAt, and range, which are straightforward.

Blog | Support | Training | Contact | API | Status | Twitter | Help | Security
© 2010 GitHub Inc. All rights reserved. | Terms of Service | Privacy Policy
Powered by the Dedicated Servers and
Cloud Computing of Rackspace Hosting®
Dedicated Server