Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to ensure correct "this" with Promise.promisify?

I am trying to refactory my nodejs server using promises with Bluebird library, but I am stuck in a simple problem.

After to get the users from my db, I want to list all notification class associated with this user:

Bad Way (working...)

adapter.getUsers(function(users){
    users.rows.forEach(function(item){
        user = item.username;
        adapter.getNotifications(user, function(notificationList){
            console.log(notificationList);
        })
    });
});

Elegant Tentative Way (not working...)

var getNotifications = Promise.promisify(adapter.getNotifications);
adapter.getUsers().then(function(users) {
    users.rows.forEach(function(item){
        var dbUser = "sigalei/" + item.value.name;
        console.log(dbUser);
        return getNotifications(dbUser);
    });
}).then(function(result){
    console.log(result);
    console.log("NOTIFICATIONLIST");
});

However when I execute this code I get this error inside my getNotification method:

Unhandled rejection TypeError: Cannot read property 'nano' of undefined at Adapter.getNotifications (/Users/DaniloOliveira/Workspace/sigalei-api/api/tools/couchdb-adapter.js:387:30) at tryCatcher (/Users/DaniloOliveira/Workspace/sigalei-api/node_modules/bluebird/js/main/util.js:26:23)

EDIT

After the user2864740`s precious comments, I noticed that the error is related with some scope problem. So, why after to use promisify method, the method dont getNotifications recognize the "this" env variable?

var Adapter = module.exports = function(config) {
    this.nano = require('nano')({
        url: url,
        request_defaults: config.request_defaults
    });
};

Adapter.prototype.getNotifications = function(userDb, done) {

    var that = this;
    console.log(that);
    var userDbInstance = that.nano.use(userDb);
    userDbInstance.view('_notificacao', 'lista',
      {start_key: "[false]", end_key: "[false,{}]"},
      function(err, body) {
        if(err){ done(err); }
        done(body);
    });

};
like image 358
Danilo Oliveira Avatar asked Sep 10 '15 19:09

Danilo Oliveira


People also ask

What does promise Promisify do?

promisify. Returns a function that will wrap the given nodeFunction . Instead of taking a callback, the returned function will return a promise whose fate is decided by the callback behavior of the given node function.

What does Promisify mean?

promisify() method basically takes a function as an input that follows the common Node. js callback style, i.e., with a (err, value) and returns a version of the same that returns a promise instead of a callback.

How do you Promisify callbacks?

promisify() function is not available. The idea is to create a new Promise object that wraps around the callback function. If the callback function returns an error, we reject the Promise with the error. If the callback function returns non-error output, we resolve the Promise with the output.


2 Answers

This is just the very common problem of calling "unbound" methods.
You can pass the context as an option to Promise.promisify to have it bound:

var getNotifications = Promise.promisify(adapter.getNotifications, {context: adapter});

Alternatively, you'd need to .bind() the method, or call the new getNotifications function on the adapter (using .call()). You might also consider using Promise.promisifyAll(adapater) and then just calling adapter.getNotificationsAsync(…).

Notice that this still doesn't work. You cannot simply create promises in a loop - you need to await them explicitly and return a promise from the then callback, otherwise just the undefined value you returned will be passed to the next callback immediately.

adapter.getUsers().then(function(users) {
    return Promise.all(users.rows.map(function(item){
        var dbUser = "sigalei/" + item.value.name;
        console.log(dbUser);
        return getNotifications(dbUser);
    }));
}).then(function(results) {
    for (var i=0; i<results.length; i++)
        console.log("result:", results[i]);
});

Instead of Promise.all(users.rows.map(…)), in Bluebird you can also use Promise.map(users.rows, …).

like image 116
Bergi Avatar answered Oct 29 '22 00:10

Bergi


What about simply

var getNotifications = Promise.promisify(adapter.getNotifications.bind(adapter));

or possibly

var getNotifications = Promise.promisify(function () {
    return adapter.getNotifications.apply(adapter, arguments);
});

?

I'm not sure I understand your problem well, but this should make sure this is bound and not undefined when you do return getNotifications(dbUser);

like image 35
jrsala Avatar answered Oct 29 '22 01:10

jrsala