Как правилно да напиша модул, който да е съвместим с `PromisifyAll` на Bluebird

Да кажем, че в модул node.js, moduleA.js, имам следния обект с куп асинхронни функции в стил възел:

// moduleA.js

var init = function (data, callback) {
    return callback(null, data.params );
};

var delay = function(data, callback) {
    setTimeout(function(){
        callback(null, data);
    }, 1000*3);
}


var reverse = function(data, callback) {
    var j,
        d = {};

    for(j in data) {
        d[ data[j] ] = j;
    }

    callback(null, d);

}

module.exports = {
    init: init,
    delay: delay,
    reverse: reverse
};

Консумирам moduleA.js в main.js и мога успешно promisify всеки метод поотделно, като:

// main.js
var Promise = require('bluebird'),
    modA = require('./moduleA') );

var data = {
    "a": "one",
    "b": "two",
    "c": "three",
    "d": "four"
};


Promise.promisify(modA.init)(data)

    .then( Promise.promisify(modA.delay) )

    .then( Promise.promisify(modA.reverse) )

    .then(function(data){

        res.send(data);

    }).catch(function(e){
        next(e);
    });

Горният код работи добре, но е по-подробен от желаното.

Въпросът ми е как да модифицирам своя модул, за да позволя на Promise.promisifyAll да работи правилно върху целия експортиран обект? Искам да избегна promisification вътре в модула и да позволя на другите по избор да го promisify при консумация.

Неуспешно опитах много варианти на следното:

// main.js
var Promise = require('bluebird'),
    modA = require('./moduleA') ),
    modAPromised = Promise.promisifyAll(modA);

var data = {
    "a": "one",
    "b": "two",
    "c": "three",
    "d": "four"
};

modAPromised.initAsync(data)

    .then(modAPromised.delayAsync)

    .then(modAPromised.reverseAsync)

    .then(function(data){

        res.send(data);

    }).catch(function(e){
        next(e);
    });

Когато правя това, получавам грешка, която казва Cannot call method 'delay' of undefined. Promise.promisifyAll добавя всички Async функции, както се очаква:

// console.log(modAPromised);

{
    init: [Function],
    delay: [Function],
    reverse: [Function],
    initAsync: {
        [Function] __isPromisified__: true
    },
    delayAsync: {
        [Function] __isPromisified__: true
    },
    reverseAsync: {
        [Function] __isPromisified__: true
    }
}

но мисля, че нещо не е наред с контекста. Изглежда, че вътрешно delayAsync се опитва да извика this.delay, но this е недефиниран.

И така, как бих модифицирал своя модул, за да позволя на Promise.promisifyAll да работи правилно върху целия експортиран обект?

Благодаря предварително.


person Jared    schedule 03.02.2015    source източник
comment
Защо просто не направите двоен API с .nodeify? Това би било още по-лесно за потребителя   -  person Esailija    schedule 03.02.2015


Отговори (2)


promisifyAll създава методи, които зависят от this, тъй като this не е известен, когато му подадете прототип на клас, от който ще бъдат създадени множество обекти.

Например:

Promise.promisifyAll(require("redis"));

// In another file
var redis = require("redis");
var client1 = redis.createClient(...);
var client2 = redis.createClient(...);

client1.putAsync(...);
client2.putAsync(...);

Нямаше нищо за обвързване, когато "redis" беше обещано, и тези методи зависят от конкретните client екземпляри. .putAsync не може просто да извика put - трябва да извика put както в контекста на client1, така и client2 в зависимост от това как е извикано putAsync.

Беше предложено решение на някой, който има същия проблем, но той така и не отговори: https://github.com/petkaantonov/bluebird/issues/470.


Вашият модул може да бъде внедрен като двоен API, използващ nodeify, по този начин потребителите дори не се нуждаят от обещанието:

var init = function (data, callback) {
    return Promise.resolve(data.params).nodeify(callback);
};

var delay = function(data, callback) {
    return Promise.delay(data, 1000 * 3).nodeify(callback);
}

var reverse = function(data, callback) {
    var j,
        d = {};

    for(j in data) {
        d[ data[j] ] = j;
    }

    return Promise.resolve(d).nodeify(callback);
}
person Esailija    schedule 03.02.2015
comment
Хм, но за тези свойства, които са статично известни, можете директно да се обърнете към метода чрез затваряне? Използване на put = redis.prototype.put; и put.call(this) вместо this.put() - person Bergi; 03.02.2015
comment
@Bergi да, но отразяващите обаждания са много по-бавни (поне бяха, когато това беше внедрено), но това можеше да се е променило досега. Ще трябва да го сравним - в такъв случай можем да избегнем добавянето на още една опция, така че благодаря за идеята. - person Esailija; 03.02.2015
comment
О, забравих колко ви е грижа за производителността :-) Можете също така да проверите метода дали изобщо използва this (все пак не правите ли декомпилация?) и ако не дори да пропуснете .call(this, . - person Bergi; 03.02.2015
comment
@Bergi Не правя декомпилация, а само компилация .. но това също е добър трик. - person Esailija; 03.02.2015

Вашият проблем е по-малко с moduleA.js и повече с използването ви в main.js.

Ако изрично извикате вашите обещани функции във вашите .then() оператори, вашият модул ще работи правилно:

// main.js
var Promise = require('bluebird'),
    modA = require('./moduleA'),
    modAPromised = Promise.promisifyAll(modA);

var data = {
    "a": "one",
    "b": "two",
    "c": "three",
    "d": "four"
};

modAPromised.initAsync(data)

    .then(function(data) {
        return modAPromised.delayAsync(data);
    })

    .then(function(data) {
        return modAPromised.reverseAsync(data);
    })

    .then(function(data){

        res.send(data);

    }).catch(function(e){
        next(e);
    });

Що се отнася до защо това е, честно казано не съм сигурен. Имам силно подозрение, че promisifyAll прави някакво контекстно обвързване, което promise не прави. Това би обяснило защо опаковането на вашите методи в чиста функция би решило проблема.

Сигурен съм, че поддържащите Bluebird ще знаят повече, ако искате да получите подробности за внедряването.

person Steven Schobert    schedule 03.02.2015