39
43

More than 5 years have passed since last update.

Node.jsでコールバック関数を書く

Posted at

最もシンプルな例

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);
39
43
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
39
43