Каков наиболее сжатый способ использования отложенного канала jQuery? - PullRequest
5 голосов
/ 22 октября 2011

Я разрабатываю API javascript, который включает в себя мой REST 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, но асинхронные.

Передача функции в любой точке, но обычно последний метод, будет вызываться изнутри, когда будет решен последний отложенный объект в цепочке.Вторая функция - это обратный вызов сбоя, который вызывается в случае отклонения любого из отложенных объектов в цепочке.

Я думаю, что мне нужно создать отложенный объект jQuery, а затем расширить его, но не уверен, что это«лучший» способ.

Так каков наилучший практический способ достижения моей минималистической цели API?По сути, я хочу, чтобы все имена методов были на 100% в пространстве имен проблемных областей и не были загрязнены множеством «когда», «сделано», «успехом» и т. Д.

И есть ли примеры подобных чистых APIЯ могу где-нибудь подражать?

Ответы [ 3 ]

2 голосов
/ 22 октября 2011

Я собираюсь оставить свою реализацию 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 у нас есть две проблемы: каждый раз, когда вы вызываете метод получения, он действительно должен возвращать новый 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. Теперь для реализации 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());
}

Сначала мы явно создаем новый отложенный объект и присоединяем к нему обработчики 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 голос
/ 22 октября 2011

Я думаю, что вам нужен конструктор запросов, где методы, которые добавляют критерии (например, «папа» и «помощник»), являются цепочечными. Кроме того, вы хотите, чтобы в любой момент вы могли передать обратный вызов, и это означает выполнить запрос.

Так что я бы сделал это так:

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); });
0 голосов
/ 24 октября 2011

У меня это отлично работает с внутренним обещанием, поэтому я сразу же создаю объекты 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);



...