Как мне правильно написать модуль, чтобы он был совместим с `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