How can I use Socket.IO with promises? - node.js

As a part of an ongoing effort, I'm changing my current callbacks technique to promises using blue-bird promise library.
I would like to implement this technique with Socket.IO as well.
How can I use Socket.IO with promises instead of callbacks?
Is there any standard way of doing it with Socket.IO? any official solution?

You might look into Q-Connection, which facilitates RPC using promises as proxies for remote objects and can use Socket.IO as a message transport.

Bluebird (and many other promise libraries) provide helper methods to wrap your node style functions to return a promise.
var readFile = Promise.promisify(require("fs").readFile);
readFile("myfile.js", "utf8").then(function(contents){ ... });
https://github.com/petkaantonov/bluebird/blob/master/API.md#promisification
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.
The node function should conform to node.js convention of accepting a
callback as last argument and calling that callback with error as the
first argument and success value on the second argument.

have a look here https://www.npmjs.com/package/socket.io-rpc
var io = require('socket.io').listen(server);
var Promise = require('bluebird');
var rpc = require('socket.io-rpc');
var rpcMaster = rpc(io, {channelTemplates: true, expressApp: app})
//channelTemplates true is default, though you can change it, I would recommend leaving it to true,
// false is good only when your channels are dynamic so there is no point in caching
.expose('myChannel', {
//plain JS function
getTime: function () {
console.log('Client ID is: ' + this.id);
return new Date();
},
//returns a promise, which when resolved will resolve promise on client-side with the result(with the middle step in JSON over socket.io)
myAsyncTest: function (param) {
var deffered = Promise.defer();
setTimeout(function(){
deffered.resolve("String generated asynchronously serverside with " + param);
},1000);
return deffered.promise;
}
});
io.sockets.on('connection', function (socket) {
rpcMaster.loadClientChannel(socket,'clientChannel').then(function (fns) {
fns.fnOnClient("calling client ").then(function (ret) {
console.log("client returned: " + ret);
});
});
});

Related

Why does 'promisify' cause node to ignore a function?

I wrote the following code:
var express = require('express');
var app = express();
var Promise = require('bluebird');
var counter = {};
counter.num = 0;
function incr(counter) {
counter.num = counter.num + 1;
}
app.get('/check', function(req, res) {
Promise.promisify(console.log)(counter.num)
.then(Promise.promisify(incr)(counter.num))
.then(console.log(counter.num));
res.end("OK");
});
app.listen(4000);
I expect the following operations to take place synchronically:
1. print counter.num (=0)
2. increment counter.num
3. print the new counter.num (=1)
This is what I get:
0 [Function]
0
Why didn't operation 2 take place? and Why do I get a "[Function]" in the console?
Your situation is not appropriate for Promise.promisify().
Assuming you are using Bluebird, then Bluebird's Promise.promisify() expects the following:
The function you're promisifying must take a callback as it last argument (often referred to as the node.js async calling convention).
That callback must be called with the argument (err, data) when the function has completed its operation.
The err value must be falsey when the function is successful and the result is passed in the data argument. The err value must be truthy when there is an error and then err is the error value.
Your use of .promisify() does not match any of these conditions.
Since the purpose of promises is to track or coordinate asynchronous operations, it does not appear that you should even be using promises for this particular code. Because all your counter operations are synchronous you can just do this:
app.get('/check', function(req, res) {
console.log(counter.num);
incr(counter);
console.log(counter.num);
res.end("OK");
});
promisify is for async functions. console.log is a sync function. Bluebird expects function to be promisified has callback function for last arguments.
And I don't see any reason you might want to use promise in your question situation.

Execute when both(!) events fire .on('end')

I have a node app that reads two files as streams. I use event.on('end') to then work with the results. The problem is I don't really know how I can wait for BOTH events to trigger 'end'.
What I have now is:
reader1.on('end', function(){
reader2.on('end',function(){
doSomething();
});
});
With small files this works, but if one of the files is very large the app aborts.
Your execution logic is somewhat flawed. You ought to do something like this instead
var checklist = [];
// checklist will contain sort of a counter
function reader_end(){
if(checklist.length == 2 )
// doSomething only if both have been added to the checklist
doSomething();
}
reader1.on('end', function() {
checklist.push('reader1');
// increment the counter
reader_end();
});
reader2.on('end', function() {
checklist.push('reader2');
reader_end();
});
Although there are libraries to better handle this sort of stuff, like Async and Promises.
With Async you'll need to use compose
var r12_done = async.compose(reader1.on, reader2.on);
r12_done('end', function(){
doSomething();
});
Edit: I just noticed that since probably reader1.on is a Stream 'end' event which doesn't have the standard callback argument signature of (err, results), this probably won't work. In that case you should just go with Promise.
With Promise you'll need to first Promisify and then join
var reader1Promise = Promise.promisify(reader1.on)('end');
var reader2Promise = Promise.promisify(reader2.on)('end');
var reader12Promise = Promise.join(reader1Promise, reader1Promise);
reader12Promise.then(function(){
doSomething();
});

NodeJS callback sequence

Folks,
I have the following function, and am wondering whats the correct way to call the callback() only when the database operation completes on all items:
function mapSomething (callback) {
_.each(someArray, function (item) {
dao.dosomething(item.foo, function (err, account) {
item.email = account.email;
});
});
callback();
},
What I need is to iterate over someArray and do a database call for each element. After replacing all items in the array, I need to only then call the callback. Ofcourse the callback is in the incorrect place right now
Thanks!
The way you currently have it, callback is executed before any of the (async) tasks finish.
The async module has an each() that allows for a final callback:
var async = require('async');
// ...
function mapSomething (callback) {
async.each(someArray, function(item, cb) {
dao.dosomething(item.foo, function(err, account) {
if (err)
return cb(err);
item.email = account.email;
cb();
});
}, callback);
}
This will not wait for all your database calls to be done before calling callback(). It will launch all the database calls at once in parallel (I'm assuming that's what dao.dosomething() is). And, then immediately call callback() before any of the database calls have finished.
You have several choices to solve the problem.
You can use promises (by promisifying the database call) and then use Promise.all() to wait for all the database calls to be done.
You can use the async library to manage the coordination for you.
You can keep track of when each one is done yourself and when the last one is done, call your callback.
I would recommend options 1. or 2. Personally, I prefer to use promises and since you're interfacing with a database, this is probably not the only place you're making database calls, so I'd promisify the interface (bluebird will do that for you in one function call) and then use promises.
Here's what a promise solution could look like:
var Promise = require('bluebird');
// make promise version of your DB function
// ideally, you'd promisify the whole DB API with .promisifyAll()
var dosomething = Promise.promisify(dao.dosomething, dao);
function mapSomething (callback, errCallback) {
Promise.all(_.map(someArray, function(item) {
return dosomething(item.foo).then(function (account) {
item.email = account.email;
});
}).then(callback, errCallback);
}
This assumes you want to run all the DB calls in parallel and then call the callback when they are all done.
FYI, here's a link to how Bluebird promisify's existing APIs. I use this mechanism for all async file I/O in node and it saves a ton of coding time and makes error handling much more sane. Async callbacks are a nightmare for proper error handling, especially if exceptions can be thrown from async callbacks.
P.S. You may actually want your mapSomething() function to just return a promise itself so the caller is then responsible for specifying their own .then() handler and it allows the caller to use the returned promise for their own synchronization with other things (e.g. it's just more flexible that way).
function mapSomething() {
return Promise.all(_.map(someArray, function(item) {
return dosomething(item.foo).then(function (account) {
item.email = account.email;
});
})
}
mapSomething.then(mapSucessHandler, mapErrorHandler);
I haven't tried Bluebird's .map() myself, but once you've promisified the database call, I think it would simplify it a bit more like this:
function mapSomething() {
return Promise.map(someArray, function(item) {
return dosomething(item.foo).then(function (account) {
item.email = account.email;
});
})
}
mapSomething.then(mapSucessHandler, mapErrorHandler);

Manually promisifying pg.connect with Bluebird

I want to promisify node-postgres' pg.connect method along with the inner connection.query method provided in the callback.
I can .promisify the latter, but I need to implement the first one manually (if I'm missing something here, please explain).
The thing is, I'm not sure if this code is correct or should be improved? The code is working, I just want to know if I'm using Bluebird as meant.
// aliases
var asPromise = Promise.promisify;
// save reference to original method
var connect = pg.connect.bind(pg);
// promisify method
pg.connect = function (data) {
var deferred = Promise.defer();
connect(data, function promisify(err, connection, release) {
if (err) return deferred.reject(err);
// promisify query factory
connection.query = asPromise(connection.query, connection);
// resolve promised connection
deferred.resolve([connection,release]);
});
return deferred.promise;
};
Throw all that horrible callback code away, then do this somewhere in your application initialization:
var pg = require("pg");
var Promise = require("bluebird");
Object.keys(pg).forEach(function(key) {
var Class = pg[key];
if (typeof Class === "function") {
Promise.promisifyAll(Class.prototype);
Promise.promisifyAll(Class);
}
})
Promise.promisifyAll(pg);
Later in anywhere you can use the pg module as if it was designed to use promises to begin with:
// Later
// Don't even need to require bluebird here
var pg = require("pg");
// Note how it's the pg API but with *Async suffix
pg.connectAsync(...).spread(function(connection, release) {
return connection.queryAsync("...")
.then(function(result) {
console.log("rows", result.rows);
})
.finally(function() {
// Creating a superfluous anonymous function cos I am
// unsure of your JS skill level
release();
});
});
By now there are a number of libraries which do this for you:
pg-promise - generic Promises/A+ for PG
postgres-bluebird
dbh-ph
pg-bluebird
Update for bluebird 3:
The pg.connectAsync(...).spread(function(connection, release) { ... }) call will not work anymore, because the API of bluebird has changed: http://bluebirdjs.com/docs/new-in-bluebird-3.html#promisification-api-changes .
The problem is that promisifyAll in bluebird 3 does not handle multiple arguments by default. This results in the .spread() call reporting a TypeError like the following:
TypeError: expecting an array or an iterable object but got [object Null]
To solve this, you can explicitly enable multiple arguments for connect / connectAsync. Do the following after all the promisifying stuff mentioned above:
...
pg.connectAsync = Promise.promisify(pg.connect, { multiArgs: true });
I suggest to modify Petka Antonov solution a bit
var Promise = require('bluebird');
var pg = require('pg');
Object.keys(pg).forEach(function (key) {
var Cls = null;
try {
Cls = pg[key];
if (typeof Cls === 'function') {
Promise.promisifyAll(Cls.prototype);
Promise.promisifyAll(Cls);
}
} catch (e) {
console.log(e);
}
});
Promise.promisifyAll(pg);
here 'pg[key] wrapped up in try-catch block because pg[key] can retrun error when attempt to access pg['native']

How to create and manipulate promises in Protractor?

I want to use the Node Http module to call my server directly in order to set up my Protractor tests. Http is callback based and I want to turn that into promises.
For example, I want to have this function return promise:
function callMyApi() {
var promise = // somehow create promise;
http.request({path: '/yada/yada', method: 'POST'}, function(resp) {
promise.complete(resp);
});
return promise;
}
So, the question is: what do I need to require() and put in place of "somehow create promise" for this to work?
Protractor uses WebDriver's promises and exposes that API globally on 'protractor'. So you should be able to do
var deferred = protractor.promise.defer();
return deferred.promise;
For the full WebDriverJS Promise API, see the code at https://code.google.com/p/selenium/source/browse/javascript/webdriver/promise.js
This is the wrong way to do this, but knowing about the Protractor Control Flow could help. If you want regular Javascript run in Protractor order add it through the control flow.
In this case you could use your own promise library if you want then just use browser.wait to wait for your promises to complete.
var Promise = require('bluebird');
var promises = [];
browser.controlFlow().execute(function() {
var p = new Promise...
promises.push(p);
});
browser.wait( function(){ return Promise.all(promises); }, timeoutMs );
I use this not for regular promises, but for console.log statements or doing timing for a part of a test, or even using fs to print something in a test to a file.
var startTime, duration;
browser.controlFlow().execute(function() {
startTime = new Date().getTime();
});
//Protractor code you want timed
browser.controlFlow().execute(function() {
duration = new Date().getTime() - startTime;
console.log("Duration:", duration);
});

Resources