Conjuring JQuery Deferred with monadic incantation

2019-02-17 19:17发布

Inspired by this (excellent) discussion of using Promises in javascript, I'm trying to work out how I could use Deferred to chain together async and non-async functions, to avoid paying the callback tax when using my 'Global storage' code.

I've got a few questions related to this, but I'll ask them together here, because the context is the same.

One thing I can't work out is how I can make a deferred out of something that isn't asynchronous - that is, how do I take a value, wrap it in a promise, and return it directly? (a -> M<a>)

Also, how can I take an asynchronous function and wrap it so that it returns its result directly, but wrapped in a promise? ((a -> b) -> (a -> M<b>))

Last question, for monadic freaks - is there a standard name for this function? [a] -> (a -> M<b>) -> M<[b]>

3条回答
劳资没心,怎么记你
2楼-- · 2019-02-17 19:34

Helped along by @Pointy, implementing 'lift' becomes trivial:

function unit(value) {
  var rv = $.Deferred();
  rv.resolveWith(null, [value]);
  return rv.promise();
}
function lift(fn) {
  return function(x) {
    return unit(fn(x));
  };
}
lift(alert)("hello");

function bind(fn) {
   return function(x) {
     return x.done(function(y) { return fn(y); });
   }
}

function compose(f, g) { return function(x) { g(f(x)); } };
function twice(x) { return 2 * x; }
var alert2 = compose(bind(lift(twice)), bind(lift(alert)));

alert2(unit(4)); //error at the end because alert doesn't return any values

Now I just have to work out how to implement [a] -> (a -> M<b>) -> M<[b]>, and what to call it!

EDIT, I ended up implementing (a -> M<b>) -> ([a] -> M<[b]>) instead, it looks like this:

function listBind(fn) {
    return function(a) {        
      var Mb = $.Deferred();
      var b = [], pending = a.length;
      var callback = function(i,val) {
         b[i] = val;
         if(--pending == 0) Mb.resolve(b);
      };
      for(var i = 0, n = a.length; i < n; i++) {
          (function(closure) { //ugly, but have to use closure to 'copy' i
             fn(a[closure]).done(function(val) { callback(closure, val); })
          })(i);
      }
      return Mb.promise();
    };
}

So, given a function which gets one deferred item, this function listBind returns a new function which accepts an array of values, and uses them to return another list of values inside a deferred item.

查看更多
淡お忘
3楼-- · 2019-02-17 19:45

Wrapping a value into a promise is as simple as using $.when:

var promise = $.when( value );

Also, as of jQuery 1.6, you have a very simple chaining method (pipe):

var chained = functionThatReturnsAPromise().pipe(function( resolveValue ) {
        return functionThatReturnsAnotherPromise( resolveValue );
    });

chained.done(function() {
    // Both asynchronous operations done in sequence
});

Hope this helps.

查看更多
贪生不怕死
4楼-- · 2019-02-17 19:48

I think the way that you'd turn a value into a Promise is to just "pre-succeed" the Deferred:

function v2p(value) {
  var rv = $.Deferred();
  rv.resolveWith(null, [value]);
  return rv.promise();
}

Now passing a function to ".done()" will result in the function being immediately called with the value.

v2p("hello").done(function(value) { alert(value); });

would immediately alert "hello".

查看更多
登录 后发表回答