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();