Otaqui.com Blog

Sheaf – a little library for serial Promise management

I’ve released sheaf, a library for looping over promises in a non-concurrent fashion. It helps you treat async operations as though they were synchronous instead (but doesn’t “block”).

This is useful if you want to use a series of asynchronous functions on a list of initial items, but want one series to complete before the next one starts.

The use case I had which prompted writing the library was loading results from a database, generating a jsdom page from each result, and taking a screenshot of each one. With 8000 results, I really didn’t want to try running them all concurrently, but since jsdom in asynchronous, I actually wanted to wait for each item to resolve before moving on to the next.

Sheaf is available via npm, and also works fine in browsers. It depends on bond to provide it’s returned promise.

The API looks like this:

var aList = ['/one.json', '/two.json']
var fn1Async = function() { /* return a promise */ }
var fn2Async = function() { /* return a promise */ }
var fn3Sync = function() { /* return a value */ }
var fn4Async = function() { /* return a promise */ }
sheaf( aList, fn1Async, fn2Async, fn3Sync, fn4Async );

The first argument to sheaf is the list of things you want to loop over. Subsequent arguments are functions that either return promises or values. The first such function is given each items from the array as sole argument, each function after that is given the return value (or promise resolution arguments) from the previous function as parameters.

Each complete cycle of the functions is run through before started again on the next member of the initial array.

A diagram might help:

Sheaf's flow

Sheaf itself returns a promise, which will notify on each iteration and resolve when all iterations are complete, e.g.

sheaf( aList, fn1Async, fn2Async, fn3Sync, fn4Async )
  .progress(function() {console.log('one loop completed', arguments})
  .done(function(newList) {console.log('all loops completed', newList)});

Since sheaf and bond are designed to work with multiple arguments, the “newList” with which the promise is resolved will be an array of arrays (one per item in the initial aList);

Sheaf can be useful if you want to perform even a single async function over and over again but wait for completion each time before running again. It’s main purpose though is to loop over asynchronous chains in a non-concurrent way. You can imagine that trying to load 8000 URLs, create 8000 DOMs pages in memory and take 8000 screenshots all at once is not a very good idea!

However, you probably don’t want to use this if you are responding to HTTP calls, since you never know how long these operations will take. It would make more sense to fire off a worker process, and then return much faster.

As I have the time I’ll be trying to write up some more examples for sheaf, so stay tuned.