thunkify する時に注意すべきたった1つのこと
結論
thunkifyをオブジェクトのメソッドに適用する時は、元のメソッドに代入するか、thisを元のオブジェクトにbindすること。
fs.readFile = thunkify(fs.readFile) // または var readFile = thunkify(fs.readFile.bind(fs));
- ただし、
fs.readFileはvar readFile = thunkify(fs.readFile.bind(fs));でも動きます。詳細は以下。 - 元のオブジェクトに影響を与えない、ということを考えると後者の方が良いかも。
原因
thunkify は、Node で generator を使ったプログラムを書く時に便利ですが、1つ気をつけることがあります。
thunkify の実装は以下のようになっています。
function thunkify(fn){
return function(){
// 省略
fn.apply(this, args); // (1)
return function(fn){
// 省略
}
}
};
上記の (1) の部分のthisが曲者で、
var obj = {
func1: function (cb) {
console.log('func1');
this.func2(cb); // (2) 動かない!
},
func2: function (cb) {
console.log('func2');
cb(null, 'func2');
}
};
こういうオブジェクトに対して、
var func1 = thunkify(obj.func1);
とやってしまうと、(2) の部分のthisがglobalスコープになってしまって、TypeError: Object #<Object> has no method 'func2'と言われてしまうのです。
ただし、fs.readFileのように内部でthisを利用しないメソッドは動いてしまうので、なかなか気が付きにくい。自分で書いたメソッドの場合は良いけど、ライブラリを利用する場合は、念の為に元のメソッドに代入するか、thisを元のオブジェクトにbindしておいた方が良さそう。
気がついた経緯から解決まで
Twitter で高速解決。
@hakobera 中で apply してるのが this だからじゃないですかね?
— Jxck (@Jxck_) 2014, 1月 16
@hakobera var some = thunkify(fs.readFile.bind(fs)); してやればいいのでは
— ミルモでポン! (@KOBA789) 2014, 1月 16
Thanks, @Jack_ さん、@KOBA789 さん。
参考資料
とてもわかりやすいジェネレータの解説
ジェネレータの解説と非同期への適用 - Block Rockin’ Codes
おまけ: エラー再現コード
var thunkify = require('thunkify');
var obj = {
func1: function (cb) {
console.log('func1');
this.func2(cb); // (2) 動かない!
},
func2: function (cb) {
console.log('func2');
cb(null, 'func2');
}
};
var func1 = thunkify(obj.func1);
//var func1 = thunkify(obj.func1.bind(obj)); // こっちならOK
function* gen() {
yield func1();
};
var g = gen();
g.next();