In JavaScript , chained method calls are very popular, and friends who use jQuery must have a deep understanding of this. This method is described in more detail in "Javascript Design Patterns". To implement chain calls of methods, you only need to let the methods defined in the prototype return references to the instance objects that call these methods. Take a look in the book This code:
(function() {
function _$(els) {
this.elements = [];
for (var i = 0, len = els.length; i < len; ++i) {
var element = els[i];
if (typeof element == 'string') {
element = document.getElementById(element);
}
this.elements.push(element);
}
};
_$.prototype = {
each: function(fn) {
for ( var i = 0, len = this.elements.length; i < len; ++i ) {
fn.call(this, this.elements[i]);
}
return this;
},
setStyle: function(prop, val) {
this.each(function(el) {
el.style[prop] = val;
});
return this;
},
show: function() {
var that = this;
this.each(function(el) {
that.setStyle('display', 'block');
});
return this;
},
addEvent: function(type, fn) {
var add = function(el) {
if (window.addEventListener) {
el.addEventListener(type, fn, false);
}
else if (window.attachEvent) {
el.attachEvent('on'+type, fn);
}
};
this.each(function(el) {
add(el);
});
return this;
}
};
window.$ = function() {
return new _$(arguments);
};
})();
As you can see, each method ends with "return this", which passes the object of the calling method to the next method in the chain. However, if the data we want to operate is obtained through an asynchronous request, how to maintain the chain of method calls? Dustin Diaz provides us with a way to ensure chained method calls. He is also one of the authors of the book "Javascript Design Patterns".
He first constructed a Queue object, namely:
Then use it as a tool to build our asynchronous method queue chain. With this tool, it is easy to build a jQuery plugin that fetches content from the server and appends it to a selector.
In this way, we can get the content asynchronously and continue our chain of calls.
$("<div/>")
.fetch('/server/navigation.html')
.addClass('column')
.appendTo('#side');
Check out the demo page to see the effect.
What to do if there are many items in a queue waiting to be acted upon on a server-side response? The author constructed such a method, which is worth referring to:
In this way, we can call it in the following way:
fetchTweet(url).linkify().filterBadWords().appendTo('#status');
At this point, we already know how to implement asynchronous method chaining, but some questions raised by some comments at the bottom of " Asynchronous method queue chaining in JavaScript " are worth thinking about. The plug-in $.fn.fetch only needs to append the returned content to the element. Is Queue necessary? Moreover, $.fn.load in jQuery can be completely implemented. If only one callback function is used in Queue, it can be written like this:
(function($) {
$.fn.fetch = function(url) {
var queue = new Queue;
this.each(function() {
var el = this;
$.ajax({
url: url,
type: 'get',
dataType: 'json',
success: function(resp) {
$(el).html(resp['text1']);
}
});
});
return this;
};
})(jQuery);
I wonder what you think?
function fetchTweet(url) {
this.queue = new Queue;
this.tweet = "";
var self = this;
ajax(url, function(resp) {
self.tweet = resp;
self.queue.flush(this);
});
}
fetchTweet.prototype = {
linkify: function() {
this.queue.add(function(self) {
self.tweet = self.tweet.replace(/b@(w{1,20}b/g, '$1');
});
return this;
},
filterBadWords: function() {
this.queue.add(function(self) {
self.tweet = self.tweet.replace(/b(fuck|shit|piss)b/g, "");
});
return this;
},
appendTo: function(selector) {
this.queue.add(function(self) {
$(self.tweet).appendTo(selector);
});
return this;
}
};
(function($) {
$.fn.fetch = function(url) {
var queue = new Queue;
this.each(function() {
var el = this;
queue.add(function(resp) {
$(el).html(resp);
});
});
$.ajax({
url: url,
dataType: 'html',
success: function(html) {
queue.flush(html);
}
});
return this;
};
})(jQuery);
function Queue() {
// store your callbacks
this._methods = [];
// keep a reference to your response
this._response = null;
// all queues start off unflushed
this._flushed = false;
}
Queue.prototype = {
// adds callbacks to your queue
add: function(fn) {
// if the queue had been flushed, return immediately
if (this._flushed) {
fn(this._response);
// otherwise push it on the queue
} else {
this._methods.push(fn);
}
},
flush: function(resp) {
// note: flush only ever happens once
if (this._flushed) {
return;
}
// store your response for subsequent calls after flush()
this._response = resp;
// mark that it's been flushed
this._flushed = true;
// shift 'em out and call 'em back
while (this._methods[0]) {
this._methods.shift()(resp);
}
}
};