Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

最もシンプルな例

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);
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした