jquery 1.7's callbacks feature demystified
function fn1(val) {
console.log('f1 says: ' + val);
}
function fn2(val) {
console.log('f2 says: ' + val)
}
var callbacks = $.Callbacks();
callbacks.add(fn1);
callbacks.fire('foo!');
// output: f1 says foo!
callbacks.add(fn2);
callbacks.fire('bar!');
// output:
// f1 says: bar!
// f2 says: bar!
flags:
"once" – ensure the callback list can only be called once (整個list的funtion只會被執行一次, 後來加進來的也不會執行)
"memory" – ensure if the list was already fired, adding more callbacks will have it called with the latest fired value 新加入的 function 先執行已經fire的event, list 裡面的 funtion 再執行
"unique" – ensure a callback can only be added to the list once
"stopOnFalse" – interrupt callings when a particular callback returns false
flags - once:
var callbacks = $.Callbacks("once");
callbacks.add(fn1);
callbacks.fire('foo');
callbacks.add(fn2);
callbacks.fire('bar');
callbacks.remove(fn2);
callbacks.fire('foobar');
// ouput:
// foo
flags - memory:
var callbacks = $.Callbacks('memory');
callbacks.add(fn1);
callbacks.fire('foo');
callbacks.add(fn2);
callbacks.fire(''bar'');
callbacks.remove(fn2);
callbacks.fire('foobar');
/*
fn1 says: foo <- first fire
fn2 says: foo <- second fire. 新加入的 fn2 先執行已經fire的event, list 裡面的 funtion 再執行
fn1 says: bar
fn2 says: bar
fn1 says: foobar <- third fire
*/
flags - unique:
var callbacks = $.Callbacks('unique');
callbacks.add(fn1);
callbacks.fire('foo);
callbacks.add(fn1); // repeat addition
callbacks.add(fn2);
callbacks.fire('bar');
callbacks.remove(fn2);
callbacks.fire('foobar');
/*
fn1 says: foo <- first fire
fn1 says: bar <- second fire
fn2 says: bar
fn1 says: foobar <- third fire
*/
flags - stopOnFalse:
function fn1(val) {
console.log('f1 says: ' + val);
return false;
}
function fn2(val) {
console.log('f2 says: ' + val)
return false;
}
var callbacks = $.Callbacks('stopOnFalse');
callbacks.add(fn1);
callbacks.fire('foo');
callbacks.add(fn2);
callbacks.fire('bar');
callbacks.remove(fn2);
callbacks.fire('foobar');
/*
fn1 says: foo <- first fire
fn1 says: bar <- second fire
fn1 says: foobar <- third fire
*/
Flag combinations are internally used with $.Callbacks in jQuery for the .done() and .fail() buckets on a Deferred – both of which use "memory once".
pub/sub implementation
var topics = {};
jQuery.Topic = function( id ) {
var callbacks,
method,
topic = id && topics[ id ];
if ( !topic ) {
callbacks = jQuery.Callbacks();
topic = {
publish: callbacks.fire,
subscribe: callbacks.add,
unsubscribe: callbacks.remove
};
if ( id ) {
topics[ id ] = topic;
}
}
return topic;
};
// Subscribers
$.Topic( 'mailArrived' ).subscribe( fn1 );
$.Topic( 'mailArrived' ).subscribe( fn2 );
$.Topic( 'mailSent' ).subscribe( fn1 );
// Publisher
$.Topic( 'mailArrived' ).publish( 'hello world!' );
$.Topic( 'mailSent' ).publish( 'woo! mail!' );
// Here, 'hello world!' gets pushed to fn1 and fn2
// when the 'mailArrived' notification is published
// with 'woo! mail!' also being pushed to fn1 when
// the 'mailSent' notification is published.
/*
output:
hello world!
fn2 says: hello world!
woo! mail!
*/
Whilst this is great, we can take this pub/sub implementation further. Using $.Deferreds, we can ensure that publishers only publish notifications for subscribers once particular tasks have been completed (resolved). See the below code sample for some further comments on how this could be used in practice:
// subscribe to the mailArrived notification
$.Topic( 'mailArrived' ).subscribe( fn1 );
// create a new instance of Deferreds
var dfd = $.Deferred();
// define a new topic (without directly publishing)
var topic = $.Topic( 'mailArrived' );
// when the deferred has been resolved, we'll
// then publish a notification to subscribers
dfd.done( topic.publish );
// here we're resolving the Deferred with a message
// that will be passed back to subscribers. We could
// easily integrate this into a more complex routine
// (eg. waiting on an ajax call to complete) so that
// we only published once the task finished.
dfd.resolve( 'its been published!' );