在node.js中尝试使用q链接mongodb函数时出错,如下所示:

Q.ninvoke(MongoClient, 'connect', 'mongodb://127.0.0.1:27017/mydb')
.then(function(db){
    return Q
    .ninvoke(db, 'createCollection', 'mycollection')
    .ninvoke(db.collection('mycollection'), 'createIndex', {id: 1}) // error occurs here
    .ninvoke(db, 'close')
    .then(function(){...})
});

我收到的错误消息是:
TypeError: Cannot call method 'apply' of undefined
  at Promise.post (/path/to/my/project/node_modules/q/q.js:1157:36)
  at Promise.promise.promiseDispatch (/path/to/my/project/node_modules/q/q.js:784:41)
  at /path/to/my/project/node_modules/q/q.js:600:44
  at runSingle (/path/to/my/project/node_modules/q/q.js:133:13)
  at flush (/path/to/my/project/node_modules/q/q.js:121:13)
  at process._tickCallback (node.js:442:13)

根据消息,q.js中的1157行是关于:
Q.fulfill = fulfill;
function fulfill(value) {
    return Promise({
    ...
    "post": function (name, args) {
        if (name === null || name === void 0) {
            return value.apply(void 0, args);
        } else {
            return value[name].apply(value, args); // error occurs here: line 1157
        }
    }

虽然我不知道q内部发生了什么,但我猜db.collection('mycollection')在第1157行中没有正确地传递为value。我在Github of q上提出了这个问题,但没有任何回应。
如果我这样更改代码,一切都会恢复正常:
Q.ninvoke(MongoClient, 'connect', 'mongodb://127.0.0.1:27017/mydb')
.then(function(db){
    return Q
    .ninvoke(db, 'createCollection', 'mycollection')
    .then(function(){
        return Q.ninvoke(db.collection('mycollection'), 'createIndex', {id: 1}) // no error this time
        .then(function(){
             return Q.ninvoke(db, 'close').then(function(){...});
        });
    });
});

然而,这里来了一个金字塔,它沿着链条生长。我认为q应该像第一个例子那样支持链接。
简言之,我的问题是是否有使用q的误解,或者q中实际上有一个bug?
我使用的包版本:
node.js:v0.10.36版本
问题:1.4.0
MongoDB:2.0.31版
更新
排除MongoDB的因素,缩小问题范围如下:
var TestClass = function (name){
};

TestClass.prototype.printName = function (callback) {
    console.log('printName called');
    return callback(null);
};

var test = new TestClass('test object');

test.printName(function (err) {
    test.printName(function (err) {
        console.log('callback called');
    });
});

在这种情况下,输出应该是:
$ node q-test.js
printName called
printName called
callback called

但如果我用q来表示:
Q.ninvoke(test, 'printName')
.ninvoke(test, 'printName')
.then(function(){
    console.log('callback called');
})
.done();

结果显示输出有如下错误:
$ node test.js
printName called

/path/to/my/project/node_modules/q/q.js:155
                throw e;
                      ^
TypeError: Cannot read property '[object Object]' of undefined
    at Promise.post (/path/to/my/project/node_modules/q/q.js:1161:29)
    at Promise.promise.promiseDispatch (/path/to/my/project/node_modules/q/q.js:788:41)
    at /path/to/my/project/node_modules/q/q.js:556:49
    at runSingle (/path/to/my/project/node_modules/q/q.js:137:13)
    at flush (/path/to/my/project/node_modules/q/q.js:125:13)
    at process._tickCallback (node.js:442:13)
    at Function.Module.runMain (module.js:499:11)
    at startup (node.js:119:16)
    at node.js:929:3

最佳答案

tl;dr version:当在链中调用而不是直接调用asQ.ninvoke()时,要调用其函数的对象来自链中先前函数的结果,而不是从第一个参数调用。
阐述:
虽然我最初快速地看了一眼,认为问题在于在原始代码中,当在下面的行中调用qpromise.ninvoke()时,还不会创建集合本身。在正确的/工作的版本中,这将得到解决,因为db.collection('mycollection')绑定在创建集合之后才会发生。我现在看到,一般的问题是如何调用.ninvoke(),而不是它在链中的初始用法。
您更新的示例没有直接帮助,因为您使用的是ninvoke,但没有对其使用节点样式的回调表单,因此您一定会收到不同类型的错误。
至少在提到新的非mongo示例时,根本问题是如何在随后的调用中使用ninvoke()。在初始的.ninvoke()之后,后续的Q.ninvoke()调用应用于返回的promise时,使用promise的返回值作为第一个参数。这可能比其他任何东西都更容易说明,请回顾我对您的“printname”示例所做的更改:

var TestClass = function (name){ };

TestClass.prototype.printName = function (callback) {
    console.log('printName called');
    return callback(null,this);     // node style callback, also return this
};

var test = new TestClass('test object');

Q.ninvoke(test, 'printName')
//  in the lines below, the result of the prior line is the "implied" first
// parameter to ninvoke();
.ninvoke('printName')
.ninvoke('printName')
.then(function(){
    console.log('callback called');
})
.done();

希望这能说明发生了什么。
返回到您的原始任务,虽然我不是mongo用户,但是从查看它的文档来看,createcollection实际上返回了该集合,因此您可能可以修改以前的代码:
return Q
.ninvoke(db, 'createCollection', 'mycollection')
.ninvoke(db.collection('mycollection'), 'createIndex', {id: 1}) // error occurs here

进入
return Q
.ninvoke(db, 'createCollection', 'mycollection')
.ninvoke('createIndex', {id: 1}) // error *should not occur* here :-)

但是,我不确定您要对“close”做什么,因为据推测createindex不会只返回对db的引用。我不知道这个api,但想必您可以使用q的.ninvoke()or related functions来访问它,然后传递给.get()
有关q中实现的讨论,请参见this issue,该讨论还链接到为实现它而进行的更改,您可以确切地看到它是如何工作的。

09-25 17:35