使用Promise时挂起变量的最佳实践

时间:2014-07-07 16:59:37

标签: javascript node.js promise

我是Promises的新手,我想知道最好的做法是在保持变量的同时保持变量吗?

通过Promise连接到MongoDB非常简单:

connectToMongoDB(data).done(function(db) {

    var collection = db.collection('test_inserts');
    // do more stuff here

});

但是如果我必须连接到两个不同的数据库会发生什么?

connectToMongoDB1(data1).then(function(db1) {

    return connectToMongoDB2(data2);

}).done(function(db2) {

    var collection = db1.collection('test_inserts');
    // ERROR: db1 is undefined

});

这个错误非常有意义。但是,如何在不改变db1功能的情况下转发connectToMongoDB2(),因为我想保留connectToMongoDB2()并且我的所有承诺一般都非常通用?

我的意思是,我可以将一个物品包裹起来,存放所有相关的东西,但这看起来有点像hacky:

var relevantStuff = {};

connectToMongoDB1(data1).then(function(db1) {

    relevantStuff.db1 = db1;
    return connectToMongoDB2(data2);

}).done(function(db2) {

    var collection = relevantStuff.db1.collection('test_inserts');
    // do more stuff here

});

最佳做法是什么?

3 个答案:

答案 0 :(得分:12)

注意:我在这个答案中使用bluebird

有三种方法可以做你想做的事:闭包,绑定和Promise.using

关闭是@Sukima展示的方式。

function promisedFunc() {
    var db;
    return getCollection().then(function(col) {
        db = col;
        return db.query(stuff);
    }).then(function() {
        return db.query(otherStuff);
    });
}

绑定:使用Promise.bind,您可以使this成为保存值的对象。

function promisedFunc() {
    return getCollection().bind({}).then(function(col) {
        this.db = col;
        return this.db.query(stuff);
    }).then(function() {
        return this.db.query(otherStuff);
    });
}

最后,bluebird v2引入的最后一种方法是使用真正的资源管理。

function promisedFunc() {
    return Promise.using(getDb(), function(db) {
        return db.query(stuff).then(function() {
            return db.query(otherStuff);
        });
    });
}

我将进一步向下解释getDb方法。


最后一种方式提供了另一个非常有趣的好处:处理资源。例如,您经常需要为数据库资源调用close方法。 Promise.using允许您创建处理程序,在解析(或不解决)其中的承诺后运行。

要了解为什么这是一个优势,让我们回顾一下这三种方法。

关闭:

var db, close;
return getCollection().spread(function(col, done) {
    db = col;
    close = done;
    return db.query(stuff);
}).then(function() {
    return db.query(otherStuff);
}).finally(function() {
    close();
});

是的,这意味着每次使用数据库连接时都必须编写所有这些样板。别无选择。

现在让我们看看绑定方式:

return getCollection().bind({}).spread(function(col, done) {
    this.db = col;
    this.close = done;
    return this.db.query(stuff);
}).then(function() {
    return this.db.query(otherStuff);
}).finally(function() {
    this.close();
});

然而,现在,这可以模块化。

var db = {
    getDb: function() { return getCollection().bind({}); },
    close: function() { this.close(); }
};

return db.getDb().then(function() {
    return this.db.query(stuff);
}).then(function() {
    return this.db.query(otherStuff);
}).finally(db.close);

这已经好多了!但我们仍然需要考虑使用finally

然后,蓝鸟,Promise.using引入的方式。通过这种方式宣布:

function getDb() {
    var close;
    return getCollection().spread(function(db, done) {
        close = done;
        return db;
    }).disposer(function() {
        close();
    });
}

您可以像以前一样使用它:

return Promise.using(getDb(), function(db) {
    return db.query(stuff).then(function() {
        return db.query(otherStuff);
    });
});

无需考虑finally,也无需考虑样板。

答案 1 :(得分:2)

通常,如果您管理多个返回值(在您的情况下),它将以某种形式封装。以下是一些受欢迎的选项:

  • 外部范围变量
  • 在每个链接函数中返回多个值的数组
  • 或使用一个对象来表示各种状态,并使用每个链接函数返回该对象的实例。

外部范围变量:

function promisedFunc() {
  var db;
  return getCollection()
    .then(function(db1) {
      db = db1;
      // Do stuff
      return stuff;
    })
    .then(function(db1) {
      // Do stuff
      return db.stuff;
    });
}

返回多个值:

function getConnection() {
  return getCollection()
    .then(function(db1) {
      // Do stuff
      return [stuff, db1];
    })
    .then(function(args) {
      var stuff = args[0];
      var db = args[1];
      // Do stuff
      return [db.moreStuff, db];
    });
}

物件:

function getConnection() {
  function DB(db1, stuff) {
    if (db1 instanceof DB) { return new DB(db1.db1, stuff); }
    this.db1 = db1;
    this.stuff = stuff;
  }
  // Add to prototype etc.
  return getCollection()
    .then(function(db1) {
      // Do stuff
      return new DB(db1, stuff);
    })
    .then(function(db) {
      // Do stuff
      return new DB(db, stuff);
    });
}

答案 2 :(得分:1)

以下是我需要进行范围访问的一般情况。我使用承诺作为他们的代理人。其他答案忽略了我个人经常使用的这种技术,我认为这是一个值得考虑的替代方案。

假设蓝鸟:

var db1 = connectToMongoDB(data1);
var db2 = connectToMongoDB(data2); //  if needs sequencing, do db1.then...

Promise.join(db1,db2,function(db1,db2){
      // access both connections here, here both promises are available
      // in native promises this is Promise.all([db1,db2]).then(...)
});

如果您不需要等待,一切看起来都是串行的,如果您确实需要等待DB1并且无法并行连接,则不会涉及嵌套:

var db1 = connectToMongoDB(data1);
var db2 = db1.then(function(data){ data2 = data[0]; connectToMongoDB(data2);});

关于资源管理 - 查看Florian的好答案 - 虽然在这种情况下,MongoDB连接是持久的,你应该在应用程序中打开/关闭它们一次(通常)。