Promisify imported class (constructor) with bluebird in ES6 + babel

安稳与你 提交于 2020-01-23 06:30:28

问题


Suppose I created or have a node.js library lib.js

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

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

The important part is that the classes' constructor needs to accept a callback as it does database connections and file I/O. If I now import and use the library callback-style, everything is fine (see c1 below).

I would really like to promisify the library where I use it to make object construction more convenient (in reality it's a whole bunch of classes and methods).

However, I can't find a way to new the class properly in a promise-safe nicely.

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 = ???

How would I do this best? Changing library signature isn't a good option, creating factory methods also seems to be ugly.


回答1:


I feel strongly about this so I'll start with it: Don't do IO in constructors it's a bad idea to bind io and construction together.

That said, if you must do this because the library is out of your control and are OK with losing the ability to build objects in a sync way, you can:

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

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

And when promisifying:

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

Which would let you both use the return value and the promise, the promise will only have a then so you might want to Promise.resolve it to get a "real" promise rather than an object with properties and a promise.

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

This can be extracted to a pattern:

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



回答2:


You can't directly promisify a constructor (that I'm aware of), but you can trivially work around that with a factory method:

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

I don't think there's a prettier way, and a well-constructed factory shouldn't be too ugly. You could generalize the factory to take any constructor of that form, but then you're approaching full DI and it may be worth finding a promise-friendly DI library.



来源:https://stackoverflow.com/questions/31392123/promisify-imported-class-constructor-with-bluebird-in-es6-babel

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!