5

我正在设计一个包装我的 REST API 的 javascript API。我通常希望避免大量冗长和令人困惑的嵌套回调,并且一直在阅读延迟 jQuery 的优点。

让我们想象一下我的库“myLib”,它代表人物对象和在人物对象之间遍历的方式。它有一堆方法,如“爸爸”、“老板”、“助理”等,需要执行 ajax 请求来查找一些数据并返回另一个相关的“人”对象。但我希望他们返回一个延迟对象,该对象也有 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%,并且不会被大量的“何时”、“完成”、“成功”等污染。

有没有我可以在某处模拟的类似干净 API 的例子?

4

3 回答 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 方法时,它都应该返回一个 new Person.Chain,并且这个 newPerson.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. 现在,要实施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');
});

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

大警告,这些代码都没有经过测试。但是,从理论上讲,这是一种可行的方法。

于 2011-10-22T15:11:19.153 回答
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); });
于 2011-10-22T17:31:17.133 回答
0

我已经通过内部承诺完美地完成了这项工作,因此我立即创建了一个没有数据的 Person 对象。它所包含的只是对以后数据的承诺。方法类似于 parent() 创建一个新的承诺,该承诺链脱离当前承诺。可能有一种方法可以使用 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);



于 2011-10-23T22:53:44.510 回答