2015-07-13 15 views
5

nehme ich erstellt oder eine node.js Bibliothek lib.jsPromisify importierte Klasse (Konstruktor) mit drossel in ES6 + babel

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

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

Der wichtige Teil ist, dass die Klassen Konstruktor einen Rückruf akzeptieren muss, wie es funktioniert Datenbankverbindungen und Datei-I/O. Wenn ich jetzt den Callback-Stil der Bibliothek importiere und verwende, ist alles in Ordnung (siehe unten c1).

Ich würde wirklich gerne die Bibliothek promitrifizieren, wo ich es Objektkonstruktion bequemer (in Wirklichkeit ist es eine ganze Reihe von Klassen und Methoden) zu machen.

Allerdings kann ich keinen Weg finden, new die Klasse richtig in einem Versprechen sicher schön.

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

Wie würde ich das am besten machen? Das Ändern der Bibliothekssignatur ist keine gute Option, das Erstellen von Factory-Methoden scheint ebenfalls hässlich zu sein.

+0

nicht tun IO es eine schlechte Idee ist zusammen io und Konstruktion zu binden:

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

Dies kann zu einem Muster extrahiert werden. –

+0

Stilistisch haben Sie wahrscheinlich Recht. Aber ist das nicht das, was der Knoten Redis-Bibliotheken und viele andere Bibliotheken auch (implizit) tun? Es ist auch nicht so, dass ich blockiere oder irgendetwas, nur Aktionen auszulösen. – left4bread

+1

@ left4bread Das Ausführen von IO (selbst wenn nur Verbindungen hergestellt werden) in Ihrem Konstruktor führt Sie normalerweise zu Initialisierungsschleifen in kurzer Reihenfolge. Wenn Sie die IO-Arbeit in eine Methode aufteilen ('r = new Database(); r.openPool();') kann Ihr Code viel einfacher und testbarer werden. – ssube

Antwort

6

Ich fühle mich stark darüber, also werde ich damit anfangen: Do not IO in Konstruktoren ist es eine schlechte Idee, io und Konstruktion zusammen zu binden.

Das heißt, wenn Sie Muss dies tun, weil die Bibliothek aus Ihrer Kontrolle und sind OK mit der Fähigkeit zu verlieren, Objekte in einer Sync-Art und Weise können Sie bauen:

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

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

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

Welche Sie verwenden beide den Rückgabewert und das Versprechen, das Versprechen, nur lassen würde werden, haben eine then so möchten Sie vielleicht Promise.resolve es eher eine „echte“ Versprechen zu bekommen t Han ein Objekt mit Eigenschaften und ein Versprechen.

in Konstrukteuren
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; 
    } 
} 
+0

In diesem speziellen Beispiel ist die Lib unter meiner Kontrolle, jedoch würde die Synchronisierung Initialisierung verlieren hässlich sein. Allerdings akzeptiere ich es, da es mich überzeugt hat (und Bergis Link oben), dass ich einfach eine '.init (Callback)' Methode hinzufügen sollte. – left4bread

+1

@ left4bread nur um klar zu sein: die Lösung hier ist _exactly_ was Sie fragen, Verlust der Synchronisation Initialisierung ist ein Nebenprodukt der Verschmelzung und Erstellung - ich empfehle dagegen, es ist das gleiche Problem bei der Verwendung von Rückrufen. –

+0

@BenjaminGruenbaum Du verpasst einen schließenden Paren im 'neuen alten' Teil. – neverfox

0

Sie können nicht direkt einen Konstruktor promisify (das ich kenne), aber Sie können mit einer Factory-Methode triviales Umgehen dass:

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

Ich glaube nicht, dass es eine schönere Art und Weise, und eine gut gebaute Fabrik sollte nicht zu hässlich sein. Sie könnten die Factory verallgemeinern, um jeden Konstruktor dieses Formulars zu übernehmen, aber dann nähern Sie sich der vollständigen DI und es lohnt sich, eine verpflichtungsfreundliche DI-Bibliothek zu finden.

+0

Sie lösen mit der Auflösung o_0? –

+0

Auch OP sagt, er weiß schon, wie man das macht, also gibt es das. –

+0

@BenjaminGruenbaum Kommentar warum Fabriken am besten sind. Ich löse auf, weil der Konstruktor einen (möglicherweise asynchronen) Callback ausführt; Gibt es einen besseren Weg? – ssube

Verwandte Themen