使用 Promises 时保留变量的最佳实践 [重复]

Posted

技术标签:

【中文标题】使用 Promises 时保留变量的最佳实践 [重复]【英文标题】:Best practice to hang on to variables when using Promises [duplicate] 【发布时间】:2014-08-28 05:57:32 【问题描述】:

我是 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

);

这个错误完全有道理。但是我如何在不改变我的connectToMongoDB2() 函数的情况下转发db1,因为我想保持connectToMongoDB2() 和我所有的承诺都非常通用?

我的意思是,我可以包裹一个对象来存储所有相关的东西,但这看起来有点老套:

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

);

最佳做法是什么?

【问题讨论】:

嵌套回调。不完全是。其他所有东西(除了生成器)都会看起来很老套。 查看 Bluebird 中的 Promise.using - 这是一个很好的问题,当您在异常条件下处理多个资源时,这会得到 很多 诡计。 另外,像 Bluebird 这样的一些 Promise 库包含可以让您更轻松地执行此操作的方法(例如 .bind) - 我们可以假设一个特定的 Promise 库吗? @BenjaminGruenbaum:我正在使用 Bluebird 来构建我的承诺。我必须通读它的文档。感谢您的提示! 【参考方案1】:

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

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

外部作用域变量:

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

【讨论】:

你实际上只使用了一种方法:闭包。还有另外两种方式:绑定和Promise.using 您可以使用.spread 来获得多值结果解决方案。 @Bergi yes spread 将是一个非常好的使用模式。我不知道 OP 的 promise 库是否支持 spread【参考方案2】:

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

有3种方法可以做你想做的事:闭包、绑定和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 允许您创建处理程序,一旦其中的承诺被解决(或不被解决)就运行。

要了解为什么这是一个优势,让我们回顾一下实现这一点的 3 种方法。

关闭:

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,也无需样板。

【讨论】:

Promise.using 方式在您连接到多个数据库时确实是最优越的,在这种情况下它会为您处理资源管理(以防一个数据库发生故障)。 @BenjaminGruenbaum 编辑了答案,添加了一个简单的解释,为什么 Promise.using 更好,即使只有一个数据库。 感谢弗洛里安!前两个例子是微不足道的,但最后一个让我有点摸不着头脑。必须深入研究这个!【参考方案3】:

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

假设蓝鸟:

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 连接被构建为持久的,并且您应该(通常)在您的应用程序中打开/关闭它们一次。

【讨论】:

谢谢本杰明。蓝鸟太棒了!

以上是关于使用 Promises 时保留变量的最佳实践 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

活动存储:表单重新显示时保留/缓存上传文件的最佳实践

Lambda 函数的最佳实践

Laravel 迁移 - 永远保留它们?啥是最佳实践?

iOS 5 最佳实践(发布/保留?)

在 .NET 应用程序中保留配置/用户信息的最佳实践 [重复]

使用objective-c块时避免泄漏的最佳实践是啥?