【问题标题】:Promisify imported class (constructor) with bluebird in ES6 + babel在 ES6 + babel 中使用蓝鸟 Promisify 导入类(构造函数)
【发布时间】:2015-10-02 05:37:15
【问题描述】:

假设我创建或拥有一个 node.js 库lib.js

export class C {
    constructor(value, callback) {
        callback(false, `Hello ${value}`);
    }

    task(value, callback) {
        callback(false, "returned " + value);
    }
}

重要的是类的构造函数需要像处理数据库连接和文件 I/O 一样接受回调。如果我现在导入并使用库回调样式,一切都很好(参见下面的c1)。

我真的很想向我使用它的库做出承诺,以使 对象构造 更方便(实际上它是一大堆类和方法)。

但是,我无法在承诺安全的情况下很好地找到new 类的方法。

import Promise from 'bluebird';
import * as lib from './lib';


Promise.promisifyAll(lib);


// old style -- works as expected
const c1 = new lib.C("c1", (e, v) => {
    console.log(c1, e, v); 
});


// assuming c1 got initialized, .task() also works
c1.task("t1", console.log);
c1.taskAsync("t2").then(() => console.log("also works"));


// But how to do this properly with promises?
const c2 = new lib.C("c2"); c2.then(console.log); // clearly doesn't work, lack of callback
const c3 = new lib.CAsync("c3"); c3.then(console.log); // "cannot read property apply of undefined"
const c4 = ???

我怎样才能做到最好?更改库签名不是一个好选择,创建工厂方法似乎也很难看。

【问题讨论】:

  • 不要在构造函数中做 IO,将 io 和构造绑定在一起是个坏主意。
  • 风格方面你可能是对的。但这不是节点 Redis 库和许多其他库(隐式)所做的吗?另外,我不是阻止什么的,只是触发动作。
  • @left4bread 在构造函数中执行 IO(甚至只是建立连接)往往会导致您在短时间内进入初始化循环。只需将 IO 工作拆分为一个方法 (r = new Database(); r.openPool();) 即可使您的代码更简单、更可测试。

标签: javascript node.js constructor promise bluebird


【解决方案1】:

您不能直接承诺构造函数(我知道),但您可以使用工厂方法轻松解决这个问题:

function createC(value) {
  return new Promise(function (res, rej) {
    var c = new C(value, function (err, val) {
      if (err) {
        rej(err);
      } else {
        res(val); // or res(c) if you prefer
      }
    });
  });
}

我认为没有比这更漂亮的方式了,一个建造良好的工厂应该不会太丑。您可以将工厂泛化为采用该形式的任何构造函数,但随后您将接近完整的 DI,可能值得寻找一个对承诺友好的 DI 库。

【讨论】:

  • 另外 OP 说他已经知道如何做到这一点,所以就是这样。
  • @BenjaminGruenbaum 评论了为什么工厂是最好的。我正在解决,因为构造函数需要一个(可能是异步的)回调;有没有更好的办法?
  • 您正在以自身作为参数调用实现函数,您不妨使用 undefined 解析。你的意思是resolve(val)
  • @BenjaminGruenbaum 哦,是的,这是一个错字。谢谢。
【解决方案2】:

我对此感觉很强烈,所以我将从它开始:不要在构造函数中执行 IO,将 io 和构造绑定在一起是一个坏主意。

也就是说,如果您必须这样做,因为库不受您的控制,并且可以接受失去以同步方式构建对象的能力,您可以:

export class C {
    constructor(value, callback) {
        callback(false, `Hello ${value}`);
    }

    task(value, callback) {
        callback(false, "returned " + value);
    }
}

当承诺时:

import Promise from 'bluebird';
import * as lib from './lib';


Promise.promisifyAll(lib);

var old = lib.C; // reference the constructor
lib.C = function(value){ // override it
  o; // object we'll later return, populate in promise constructor
  var p = new Promise(function(resolve, reject){ 
    // the promise constructor is always sync, so the following works
    o = new old(value, function(err, data) {
      if(err) return reject(err);
      resolve(data);   
    });
  }); 
  // THIS IS THE IMPORTANT PART
  o.then = p.then.bind(p); // make the object a thenable, 
  return o
};

这会让你同时使用返回值和承诺,承诺将只有一个then,所以你可能想要Promise.resolve它来获得一个“真正的”承诺,而不是一个具有属性的对象一个承诺。

var o = new lib.C(); // get object
o.then(function(data){
    // access data
});

这可以提取成一个模式:

 function promisifyConstructor(cons){
   return function(...args) => { // new constructor function
     let o;
     let p = new Promise((resolve, reject) => {
         // delegate arguments
        o = new cons(...args, (err, data) => err ? reject(err) : resolve(data));
     });
     o.then = p.then.bind(p);
     return o;
   }
 }

【讨论】:

  • 在这个特定的例子中,lib 是在我的控制之下,但是,失去同步初始化会很难看。但是,我接受了它,因为它说服了我(以及上面的 Bergi 链接)我应该添加一个 .init(callback) 方法。
  • @left4bread 只是为了明确一点:这里的解决方案正是您所要求的,丢失同步初始化是混淆 io 和创建的副产品 - 我建议不要这样做, 使用回调时也是同样的问题。
  • @BenjaminGruenbaum 你在new old 部分缺少一个结束括号。
  • 鉴于this question,您似乎错过了.bind(p) p.then 方法。
  • 当然,我只是不确定bind 是缓解问题的最佳或最有效的方法。或者也许蓝鸟内部还有一些技巧:-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-01
  • 1970-01-01
  • 2023-03-12
  • 2017-04-19
  • 2015-12-01
  • 2017-06-29
相关资源
最近更新 更多