最もシンプルな例
StackOverflowから引用
http://stackoverflow.com/questions/2190850/create-a-custom-callback-in-javascript
simple.js
function doSomething(callback) {
// Call the callback
callback('stuff', 'goes', 'here');
}
function foo(a, b, c) {
// I'm the callback
console.log(a + " " + b + " " + c);
}
doSomething(foo);
実行するとdoSomething >> fooの順番に処理が行われます。
しかし、これを多段にすると
multi_callbacks.js
var log = '';
function step1(callback) {
log += 'step1 ';
callback();
}
function step2(callback) {
log += 'step2 ';
callback();
}
function step3(callback) {
log += 'step3 ';
callback();
}
function step4(callback) {
log += 'step4 ';
callback();
}
step1(function(){
step2(function(){
step3(function(){
step4(function(){
console.log('log:', log); //log: step1 step2 step3 step4
console.log('done');
})
})
})
});
あっという間にコールバック地獄となります。
コールバックのnestを回避する
コードがネストし過ぎて辛いので、コールバックの仕組みを作ります。
emitter.js
// Perfect JavaScriptより
var str = '';
var emitter = {
callbacks: [], //コールバック関数の置き場
register: function(fn) { // コールバック関数を登録する
this.callbacks.push(fn);
},
onOpen: function() { //発火装置
this.callbacks.forEach(function(f) {
f();
});
}
};
// emitterを使ってみる
emitter.register(function() {
str += "Sunday ";
});
emitter.register(function() {
str += "Monday ";
});
emitter.register(function() {
str += "Tuesday ";
});
emitter.onOpen();
// 結果確認
console.log(str); //Sunday Monday Tuesday
コールバック関数に、クロージャを利用する
クロージャを利用することで、コールバック関数に状態を持たせることができます。
>> クロージャの記事も書きました!
callback_closure.js
// クロージャを登録する
var emitter = {
callbacks: [],
register: function(fn) {
this.callbacks.push(fn);
},
onOpen: function() {
this.callbacks.forEach(function(f) {
f();
});
}
};
// 即時関数を使ったクロージャを登録する
emitter.register(function() {
var name = 'dog';
return (function() {
console.log(name+' is called!');
}());
});
emitter.register(function() {
var name = 'cat';
return (function() {
console.log(name+' is called!');
})();
});
emitter.onOpen();
実際はemitterのような仕組みを自力で作るのは大変なので、npmパッケージを利用したりします。
その一例ですが >> Promise-Qの使い方
2つのファイル間でコールバックする
まずはお題のサンプルを作ります。
family_simple.js
var my_family = {
members:[],
add_member: function(m){
this.members.push(m);
},
show_members: function() {
var members = this.members;
setTimeout(function(){
members.forEach(function(m){
console.log(m);
});
}, 500);
}
};
my_family.add_member('hoge'); //hogeをpush
my_family.add_member('fuga'); //fugaをpush
my_family.show_members(); //membersを順に表示する
この処理を2つのファイルに分けて、コールバックさせるよう書いてみます。
family.js
module.exports = {
members : [],
add_member: function(name) {
this.members.push(name);
},
get_members: function(callback) {
var members = this.members;
setTimeout(function(){
callback(members);
}, 500);
}
};
family_manager.js
var my_family = require('./family');
my_family.add_member('hoge');
my_family.add_member('fuga');
var callback = function(members){
members.forEach(function(name){
console.log(name);
});
};
my_family.get_members(callback);
このようにrequireとmodule.exportsを使って、異なるファイル間でcallbackのやり取りができます。
ただ、この例だとfamily.jsのmembersに直接参照できてしまうので、それを避けたい場合は次のような書き方もできます。
family2.js
module.exports = (function(){
var members = [];
function add_member(name) {
members.push(name);
}
function get_members(callback) {
setTimeout(function(){
callback(members);
}, 500);
}
return {
add_member: add_member,
get_members: get_members
};
}());
family_manager2.js
var my_family = require('./family2');
my_family.add_member('hoge');
my_family.add_member('fuga');
var callback = function(members){
members.forEach(function(name){
console.log(name);
});
};
my_family.get_members(callback);