如何简洁地使用jQuery deferred pipe?

5
我正在设计一个JavaScript API,用来封装我的REST API。我通常希望避免大量冗长和混乱的嵌套回调,并一直在阅读关于Deferred jQuery优点的文章。
假设我的库'myLib'代表人物对象及其之间的遍历方式。它有许多方法,如'dad'、'boss'、'assistant'等,这些方法需要进行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对象被解析时,它将在内部调用。第二个函数是失败回调函数,如果链中的任何一个Deferred对象被拒绝,就会调用它。

我认为我需要创建一个jQuery deferred对象,然后扩展它,但不确定这是否是“最好”的方法。

那么,实现我极简API目标的最佳实践是什么?基本上,我希望所有的方法名称都在领域问题名称空间中100%,而不是污染了很多“when”、“done”、“success”等等。

还有类似的干净API示例可以模仿吗?


我对下面的答案进行了一些重大修改(而不是创建一个新的答案)。看看它是否可行。 - Elliot Nelson
3个回答

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

我认为你想要的是一个查询构建器,其中添加条件的方法(如“dad”和“assistant”)都可以链接。此外,你希望在任何时候都可以传递回调函数,这意味着执行查询。

所以我会这样做:

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); });

这看起来不错,除了ajax调用需要按顺序完成以获取下一个ajax调用所需的数据块。我仍然可以在最后链接它们,但这只是将问题的关键移动到另一段代码中,而并没有真正解决它。 - Brendan Heywood

0

我已经使用内部 Promise 完美地解决了这个问题,所以我立即创建一个没有数据的 Person 对象。它只包含稍后获取数据的 Promise。像 parent() 这样的方法会创建一个新的 Promise,该 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);




网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接