我正在设计一个包装我的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的示例我可以在某处模拟?
答案 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对象,并将done
和fail
处理程序(如果有)附加到它。然后我们附加一个函数,该函数将调用从前一个函数返回的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);