使用jQuery延迟管道最简洁的方法是什么?

时间:2011-10-22 12:52:11

标签: jquery jquery-deferred deferred

我正在设计一个包装我的REST API的JavaScript API。我通常希望避免大量冗长和混乱的嵌套回调,并且已经阅读了Deferred jQuery的优点。

让我们想象一下我的库'myLib',它代表了人物对象和人物之间的遍历方式。它有一堆方法,比如'爸爸','老板','助手'等需要做一个ajax请求来查找一些数据并返回另一个相关的'people'对象。但我希望他们返回一个延迟对象,该对象也有myLib的方法,我可以链接在一起,编写真正简洁的简单代码,如下所示:


 myLib('me').dad().boss().assistant(function(obj){
   alert(obj.phone); // My dad's, bosses assistants phone number
 }, function(){
   alert('No such luck);
 });

这会创建一个'我'人物对象,然后进行第一次ajax调用以查找我的详细信息,然后使用该数据进行另一次调用以查找我的父母,然后再次找到我的老板,然后另一次获取助手,然后最后传递给我的回调,我处理它。有点像jQuery的链式遍历方法,但异步。

在解析链中的最后一个Deferred对象时,会在内部调用函数,但通常是最后一个方法。第二个函数是失败回调,如果链中的任何延迟对象被拒绝,则调用它。

我想我需要创建一个jQuery延迟对象然后扩展它但不确定这是否是“最佳”方式。

那么实现我的极简主义API目标的最佳实践方法是什么?基本上我希望所有的方法名称在域问题名称空间中都是100%,并且没有被大量的'when','done','success'等污染。

是否有类似清洁API的示例我可以在某处模拟?

3 个答案:

答案 0 :(得分:2)

我将单独留下我的Person实现,因为我认为它主要实现了它的目的:

function Person(o) {
  this.id = o.id;
  this.name = o.name;
}

Person.prototype.dad = function(done, fail) {
  var promise = $.getJSON('/people/' + this.id + '/dad').pipe(Person, null);
  promise.then(done, fail);
  return new Person.Chain(promise);  
};

Person.prototype.boss = function(done, fail) {
  var promise = $.getJSON('/people/' + this.id + '/boss').pipe(Person, null);
  promise.then(done, fail);
  return new Person.Chain(promise);  
};

对于Person.Chain实现,我们遇到两个问题:每次调用getter方法时,它确实应该返回一个新的Person.Chain,而新的Person.Chain应该是“嵌套”的:它需要将AJAX调用的结果链接在一起。这应该可以解决这两个问题。

这种方法需要几行胶水,所以首先要确保我们不必一遍又一遍地复制它:

Person.Chain = function(promise) {
  this.promise = promise;
};

Person.Chain.prototype.assistant = function(done, fail) {
  return this.pipe('assistant', done, fail);
};

Person.Chain.prototype.dad = function(done, fail) {
  return this.pipe('dad', done, fail);
};

Person.Chain.prototype.boss = function(done, fail) {
  return this.pipe('boss', done, fail);
};

我们只需要定义与Person上的getter方法一样多的包装器方法。现在,实施pipe

Person.Chain.prototype.pipe = function(f, done, fail) {
  var defer = new $.Deferred();
  defer.then(done, fail);

  this.promise.pipe(function(person) {
    person[f](function(person) {
      defer.resolve(person);
    }, function() {
      defer.reject();
    });
  }, function() {
    defer.reject();
  });

  return new Person.Chain(defer.promise());
}

首先,我们显式创建一个新的Deferred对象,并将donefail处理程序(如果有)附加到它。然后我们附加一个函数,该函数将调用从前一个函数返回的f上传递的任何Person(父亲,助手,老板等)。最后,当 函数解析时,我们显式解析了我们创建的Deferred对象。现在我们可以将连续的调用链接在一起,如下所示:

jake = new Person({id: 3, name: 'Jake'});

jake.dad().boss().assistant(function(person) {
  alert("Jake's dad's boss's assistant is " + person.name);
});

请注意,故障处理有点冗长,但是我们需要这样做,这样如果将一堆调用链接在一起,早期的故障仍然会将reject()调用一直传递给故障回调在最后给出。

这样做也是完全合法的:

jake.dad(function(person) {
  alert('Dad is ' + person.name);
}, function() {
  alert('Dad call failed');
}).boss(function(person) {
  alert('Jake dad boss is ' + person.name);
}, function() {
  alert('One of the calls failed');
});

如果第一次调用失败,将按顺序调用两个失败回调。如果只有最后一个失败,那么只会调用那个。

值得注意的是,此代码均未经过测试。但是,理论上,这是一种有效的方法。

答案 1 :(得分:1)

我认为你想要的是一个查询构建器,其中添加条件的方法(如“爸爸”和“助手”都是可链接的。而且,你想要它以便在任何时候都可以传递一个回调,这个意味着执行查询。

所以我会这样做:

function PersonQuery(personName) {
  this.criteria = [];
  criteria.push({name:personName});
}

PersonQuery.prototype.dad = function(doneCallback) {
    this.criteria.push({relationship:"dad"});
    _execute(doneCallback);
    return this;
}

PersonQuery.prototype.boss = function(doneCallback) {
    this.criteria.push({relationship:"boss"});
    _execute(doneCallback);
    return this;
}

PersonQuery.prototype.assistant = function(doneCallback) {
    this.criteria.push({relationship:"assistant"});
    _execute(doneCallback);
    return this;
}

PersonQuery.prototype._execute = function(doneCallback) {
    if (!doneCallback) {
       return;
    }
    $.ajax({"data": this.criteria}).success(function(responseData) {
       doneCallback(responseData);   
    });
}

然后,要使用它,您的示例将变为:

   new PersonQuery("me").dad().boss().assistant(function(obj) { 
    alert(obj.phone); });

答案 2 :(得分:0)

我已经完全使用内部承诺,所以我立即创建一个没有数据的Person对象。它包含的所有内容都是对数据的承诺。方法就像parent()创建一个新的promise,它链接当前的promise。可能有一种方法可以使用pipe()使这更简单,但还没有完全弄明白。


myLib = {
  find: function(id){
    var person = new Person();
    person.promise = $.ajax(.....);
  }
};

function Person(){
}
Person.prototype.parent(){
  var person = this;
  var parent = new Person();
  var deferred = $.Deferred();
  this.promise.then(function(data){
    var promise = $.ajax({url: '/person/'+data.parent+'/json'});
    promise.done(function(obj){
      person.data = obj.data;
      deferred.resolve(node);
    });
  }, function(error){
    deferred.fail();
  });
  parent.promise = deferred.promise();
  return parent;
}

Person.prototype.get(callback){
  this.promise.then(function(data){
    this.data = data;
    callback(this);
  });
}


Usage/Test:

myLib.find('12345').get(callback);
myLib.find('12345').parent().get(callback);