node.js fall through data cache pattern - node.js

I am looking for a clean way to structure my node.js code for the following situation. I thought of using EventEmitters to create a "workflow" type of thing. Also I thought of using one of the async libraries out there, that has been less thought out though.
Problem:
Looking for a piece of data
check cache, if found return
check db, if found return (store in cache)
get live data and return, (store in db, cache)
I mocked something up quickly using event emitters below.
var util = require("util");
var events = require('events');
var CheckForData = function() {
events.EventEmitter.call(this);
this.checkForData = function(key) {
this.emit("checkForDataRequest", key);
}
var _checkCache = function(key) {
if (key === 'cache') {
this.emit("found", {data:'cached data'});
}
else {
console.log("not found in cache "+key);
this.emit("checkDatastore", key);
}
}
var _chechDatastore = function(key) {
if (key === 'db') {
this.emit("found", {data:'db data'});
this.emit("storeCache", key, {data:'db data'});
}
else {
console.log("not found in db "+key);
this.emit("getData", key);
}
}
var _getData = function(key) {
if (key === 'live') {
this.emit("found", {data:'live data'});
this.emit("storeData", key, {data:'live data'});
}
else {
console.log("not found in live "+key);
this.emit("notFound", key);
}
}
var _storeData = function(key, data) {
this.emit("storeDb", key, data);
this.emit("storeCache", key, data);
}
var _storeDb = function(key, data) {
console.log("storing data in db. for "+key);
console.log(data);
}
var _storeCache = function(key, data) {
console.log("storing data in cache. for "+key);
console.log(data);
}
var _found = function(data) {
return data;
}
var _notFound = function(key) {
return key;
}
this.on("checkForDataRequest", _checkCache);
this.on("checkDatastore", _chechDatastore);
this.on("getData", _getData);
this.on("found", _found);
this.on("notFound", _notFound);
this.on("storeData", _storeData);
this.on("storeDb", _storeDb);
this.on("storeCache", _storeCache);
};
util.inherits(CheckForData, events.EventEmitter);
module.exports = new CheckForData();
To test it...
var checkForData = require('./check-for-data');
checkForData.on("found", function(data) {
console.log("Found data ");
console.log(data);
});
checkForData.on("notFound", function(key) {
console.log("NOOO Found data for " + key);
});
console.log("-------");
checkForData.checkForData('cache');
console.log("-------");
checkForData.checkForData('db');
console.log("-------");
checkForData.checkForData('live');
console.log("-------");
checkForData.checkForData('other');
console.log("-------");
Then async.js, i made a quick checkSeries which is basically the async.detectSeries but instead of returning the item in the collection return the result. See below...
var async = require('async');
function check_cache(key) {
console.log('checking cache');
return null;
}
function check_datastore(key) {
console.log('checking datastore');
return null;//{data: "db data"};
}
function check_api(options) {
console.log('calling api');
return {data: "live data"};
}
function exists(item, callback) {
callback(item());
}
async.checkSeries([check_cache, check_datastore, check_api], exists, function(result) {
// result now equals the first function that return data
console.log(result);
});
Any suggestions, hints, tips, ...? Is there a pattern or library that i am missing? Do you think it be possible/easier to do in Step, flow, ...? Memoize?

That seems like a lot of work for the caller and a lot of extra code that doesn't seem to be adding much value. I have something that looks like this in my model.
Foo.get = function (id, cb) {
var self = this;
// check the cache first
cache.get('foo:' + id, function (err, cacheFoo) {
// if found, just return it
if (!err && cacheFoo) {
return cb(null, cacheFoo);
}
// otherwise get from db
self.findOne({id: id}, function (err, dbFoo) {
if (err || !dbFoo) {
return cb(new Error('Foo not found', 404));
// you could do get live call here
}
// and store in cache
cache.store('foo:' + id, dbFoo);
return cb(null, dbFoo);
});
});
};
Callers can then always just call Foo.get(id, callback) and they don't have to care how it is actually retrieved. If it gets more complicated, you could use an async library (such as the aptly named async) to make the code more readable, but this should still be completely hidden from the caller.

Related

How to get redis value for a given redis key using Nodejs + Redis

I am using Nodejs + redis to Set and Get key:value pairs. I have written a sample code to set a key:value pair and then fetch it using the default readme from npm redis plugin.
My goal here is to get value from the redis server using any given key. I have followed the steps as given by npm redis plugin. I am able to log it, but since the plugin is async cant figure out a way to get a synchronous client.get request.
My code is
var redis = require("redis"),
client = redis.createClient();
const bluebird = require("bluebird");
bluebird.promisifyAll(redis.RedisClient.prototype);
bluebird.promisifyAll(redis.Multi.prototype);
const redisKey = "redis-set-key";
const redisValue = "hello-world";
client.set(redisKey, redisValue);
function getData(key) {
return client.get(key, function(err, result) {
console.log("1. Get key from redis - ", result.toString());
return result.toString();
});
}
const getRedisdata = getData(redisKey);
console.log("2. getRedisdata", getRedisdata);
Result
2. getRedisdata false
1. Get key from redis - hello-world
My goal is to get the result like this
1. Get key from redis - hello-world
2. getRedisdata hello-world
Please help me resolve this.
Found a solution, here is my resolved code
const redis = require("redis");
const client = redis.createClient();
const bluebird = require("bluebird");
bluebird.promisifyAll(redis.RedisClient.prototype);
bluebird.promisifyAll(redis.Multi.prototype);
const redisKey = "redis-set-key";
const redisValue = "hello-world";
client.set(redisKey, redisValue);
async function setKey(key, value, expire = "EX", time = 300) {
return new Promise((resolve, reject) => {
return client.set(key, value, function(err, result) {
if (result === null) {
reject("set key fail promise");
} else {
resolve(result);
}
});
});
}
async function getKey(key) {
return new Promise((resolve, reject) => {
return client.getAsync(key).then(function(res) {
if (res == null) {
reject("fail promise");
} else {
resolve(res);
}
});
});
}
async function hashGetKey(hashKey, hashvalue) {
return new Promise((resolve, reject) => {
return client.hget(hashKey, hashvalue, function(err, res) {
if (res == null) {
reject("hash key fail promise");
} else {
resolve(res.toString());
}
});
});
}
async function hashGetAllKey(hashKey) {
return new Promise((resolve, reject) => {
return client.hgetall(hashKey, function(err, res) {
if (res == null) {
reject("hash key all fail promise");
} else {
resolve(res);
}
});
});
}
async function delKey(key) {
return new Promise((resolve, reject) => {
return client.del(key, function(err, result) {
if (result === null) {
reject("delete fail promise");
} else {
resolve(result);
}
});
});
}
(async () => {
// get single key value
try {
const keyData = await getKey("string key");
console.log("Single key data:-", keyData);
} catch (error) {
console.log("Single key data error:-", error);
}
// get single hash key value
try {
const hashKeyData = await hashGetKey("hashkey", "hashtest 1");
console.log("Single hash key data:-", hashKeyData);
} catch (error) {
console.log("Single hash key data error:-", error);
}
// get all hash key values
try {
const allHashKeyData = await hashGetAllKey("hashkey");
console.log("All hash key data:-", allHashKeyData);
} catch (error) {
console.log("All hash key data error:-", error);
}
// delte single key
try {
const checkDel = await delKey("XXYYZZ!!!!");
console.log("Check key delete:-", checkDel);
} catch (error) {
console.log("Check key delete error:-", error);
}
// set single key
try {
const checkSet = await setKey("XXYYZZ", "AABBCC");
console.log("Check data setkey", checkSet);
} catch (error) {
console.log("Check data setkey error", error);
}
})();
// hget hashkey "hashtest 1"
client.hset("hashkey", "hashtest 1", "some value", redis.print);
client.hset(["hashkey", "hashtest 2", "some other value"], redis.print);
Haven't you read the Redis module's readme, it provides another way to use async/await way to make the async redis get process as sync.
const { promisify } = require("util");
const getAsync = promisify(client.get).bind(client);
getAsync.then(console.log).catch(console.error);

unable to return in node.js from a function

I have three blocks of code where block one executes first and the result of first block is passed to bloack 2 and then the final result is then passed to the third block which has to send data to the route.
But at the end the return is undefined.
function getUserKey(userRole, callback) {
//keys value is stored and returned
var keys = base.menuModel.find({ 'name' : userRole }, function (err, result) {
if (!err) {
var menu = JSON.stringify(result);
menu = JSON.parse(menu);
var menuKeys = [];
for(i = 0;i < Object.keys(menu[0].permissions[0]).length;i++) {
menuKeys.push((Object.keys(menu[0].permissions[0])[i]));
}
callback(null,menuKeys);
//returns menukeys to be shown
}
else {
return err;
}
});
}
n is holding the menu keys
function userMenuData(n, callback) {
var filterResult = base.globalMenuModel.find({"title" : { $in : n}},function (err, result) {
if (!err) {
callback(null,result);
}
else {
return err;
}
});
}
var userMenu = function(userRole,callback) {
var userMenuTemp = async.compose(userMenuData, getUserKey);
var sendData = userRole is passed and the result is obtained
userMenuTemp(userRole,function(err,result) {
return result; // data success
});
console.log(sendData); //undefined
return sendData;
}
here i want to pass sendData to route in node.js
but at the console i am getting undefined.
Thanks for any help
It's the async nature of node that's getting you. The console.log is happening before any of those functions are returned. You want to check out a library that does promises like Q http://documentup.com/kriskowal/q/

NodeJS getting stuck in callback

Im having troubles where my node app all a sudden starts to consume a lot of CPU. Im suspecting that the function below gets stuck somehow..
Client.prototype.countActiveChatsRedis = function (userID, agentID, obj, callback) {
var count = 0;
pub.keys("widgetActive:" + userID + ":*", function(err, key) {
if(err !== null) {
console.log("Redis error..... --> " + err);
callback(count, obj);
}
if(key && key.length > 0) {
pub.mget(key, function(err, data) {
if(data) {
for(var i = 0; i < data.length;i++) {
if(data[i]) {
var arr = data[i].split(",");
if(arr[2] == agentID) {
if (Number(arr[3]) > 0) {
count++;
}
}
}
}
callback(count, obj);
}
});
} else {
callback(count, obj);
}
});
}
Any ideas what the problem could be? Any case where it could avoid sending the callback?
This function runs around 50 times per second.
It is bad practice to use KEYS in a production environment. To quote the Redis master himself:
Warning: consider KEYS as a command that should only be used in
production environments with extreme care. It may ruin performance
when it is executed against large databases. This command is intended
for debugging and special operations, such as changing your keyspace
layout. Don't use KEYS in your regular application code. If you're
looking for a way to find keys in a subset of your keyspace, consider
using SCAN or sets.
Whenever you add a key with this prefix, just add it to a set called "widgetActive" the user's id or any other data you need.
you can also use HASH if you need to save some data for each entry.
you should always return your callbacks to make sure that your function terminates correctly and returns the control to the calling context:
Client.prototype.countActiveChatsRedis = function (userID, agentID, obj, callback) {
var count = 0;
pub.keys("widgetActive:" + userID + ":*", function(err, key) {
if(err !== null) {
console.log("Redis error..... --> " + err);
return callback(count, obj);
}
if(key && key.length > 0) {
pub.mget(key, function(err, data) {
if(data) {
for(var i = 0; i < data.length;i++) {
if(data[i]) {
var arr = data[i].split(",");
if(arr[2] == agentID) {
if (Number(arr[3]) > 0) {
count++;
}
}
}
}
return callback(count, obj);
}
});
} else {
return callback(count, obj);
}
});
}
It's stucked because in the case where there is no data, no callback is being called.
pub.mget(key, function(err, data) {
if(data) {
for(var i = 0; i < data.length;i++) {
if(data[i]) {
var arr = data[i].split(",");
if(arr[2] == agentID) {
if (Number(arr[3]) > 0) {
count++;
}
}
}
}
// callback(count, obj); // <==== move this callback outside of if (data)
}
callback(count, obj); // this ensures that callback always gets called
});

AngularJs $resource save throwing ERR_CONTENT_LENGTH_MISMATCH

Using AngularJs $resource for updating the existing data. calling save on resource.
This is my service.
app.factory('SubscriptionsService', function ($resource, $q) {
// $resource(url, [paramDefaults], [actions], options);
var resource = $resource('/api/subscriptions');
var factory = {};
factory.updateSubscriptions = function (updatedSubscriptions) {
console.log("SubscriptionsService: In updateSubscriptions. "+JSON.stringify( updatedSubscriptions));
var deferred = $q.defer();
resource.save(updatedSubscriptions,
function(response){
deferred.resolve(response);
},
function(response){
deferred.reject(response);
}
);
return deferred.promise;
}
return factory;
});
And API looks like the following.
exports.update = function (req, res) {
console.log("In Subscriptions API: Update invoked.");
if (req.body == null || req.body.items == null || req.body.items == 'undefined') {
return res.json({"status":0,"message":"no items present."});
}
var items = req.body.items;
console.log("About to call update on db."+items);
db.update(items,function(error,result,failedItems){
if(error)
return res.json({"status":-1,"message":error.message,"failedItem":failedItems});
return res.json({"status":1,"message":result,"failedItem":failedItems});
})
}
And controller as follows
$scope.confirmUpdates = function () {
if ($scope.updatedItems.length > 0) {
var updatedSubscriptionLevels = { "items": $scope.updatedItems };
var promise = SubscriptionsService.updateSubscriptions(updatedSubscriptionLevels);
promise.then(
function(response){
console.log("Response received from API.");
var statusCode = response.status;
console.log("statusCode: "+statusCode);
},
function(message){
console.log("Error message: "+message);
}
);
}
}
Strange part is data is getting updated. But promise receiving an error.
POST xxxxxx/testapi/subscriptions net::ERR_CONTENT_LENGTH_MISMATCH
I doubt I am not implementing/consuming resource in a right way. Can anybody advise?
Thanks.
I think I found the mistake.
There were couple of things I was not handling in a right way.
I didn't post my db code in mt question where the root cause was present.
if(itemsToUpdate!=null && itemsToUpdate.length>0){
var failedItems = []; // Holds failed items while updating.
console.log("Items to update: "+itemsToUpdate);
for(var index=0;index<itemsToUpdate.length;index++){
var item = itemsToUpdate[index];
console.log(item);
subscriptionLevels.update(
{_id:item._id},
{$set:{title:item.title,daysValid:item.daysValid,subscriptionFee:item.subscriptionFee}},
function(error,rowsEffected){
if(error){
console.log(error);
// Error occurred. Push it to failed items array.
failedItems.push({"title":item.title,"message":error.message});
}
else{
console.log("Number of rows updated: "+rowsEffected);
}
}
);
}
callback(null,"SUCCESS",failedItems);
}
else {
callback(null, "NO_ITEMS", []);
}
I missed if - else condition and invoking callback multiple times which was causing the issue.

Retrieve the last 3200 tweets of a specific user in node.js

I am new to javascript and node.js and this is my first post, so please bear with me.
I am using ntwitter to get all previous tweets of a specific user.
My problem is that if the user has more than 200 tweets, I need to create a loop and I am not sure if I do it right.
This is the async function that gets the 200 latest tweets:
exports.getUserTimeline = function(user, callback) {
twit.getUserTimeline({ screen_name: user, count: 200 }, function(err, data) {
if (err) {
return callback(err);
}
callback(err, data);
});
}
I found a solution to do this using a recursive function, but it's quite ugly.. How can I improve it ?
exports.getUserHistory = function(user, callback) {
recursiveSearch(user, callback);
function recursiveSearch(user, callback, lastId, data) {
var data = data || []
, args = {screen_name: user, count: 200};
if(typeof lastId != "undefined") args.max_id = lastId;
twit.getUserTimeline(args, function(err, subdata) {
if (err) {
console.log('Twitter search failed!');
return callback(err);
}
if (data.length !== 0) subdata.shift();
data = data.concat(subdata);
var lastId = parseInt(data[data.length-1].id_str);
if (subdata.length !== 0) {
recursiveSearch(user, callback, lastId, data);
} else {
callback(err, data);
}
});
}
}
Thank's a lot!
Update: This is the improved (refactored) function suggested by hunterloftis with two modifications:
property max_id should not be specified on the first iteration
the case where the user exists but no tweets have been posted must be handled
code:
function getUserHistory(user, done) {
var data = [];
search();
function search(lastId) {
var args = {
screen_name: user,
count: 200,
include_rts: 1
};
if(lastId) args.max_id = lastId;
twit.getUserTimeline(args, onTimeline);
function onTimeline(err, chunk) {
if (err) {
console.log('Twitter search failed!');
return done(err);
}
if (!chunk.length) {
console.log('User has not tweeted yet');
return done(err);
}
//Get rid of the first element of each iteration (not the first time)
if (data.length) chunk.shift();
data = data.concat(chunk);
var thisId = parseInt(data[data.length - 1].id_str);
if (chunk.length) return search(thisId);
console.log(data.length + ' tweets imported');
return done(undefined, data);
}
}
}
When retrieving tweets I noticed that my tweet count wasn't always the same as the 'statuses_count' property of the user. It took me some time to figure out that this difference corresponds to the number of deleted tweets :)
Does your recursive function work? Doesn't look too bad to me. I might refactor it just a little into something more like this:
function getUserHistory(user, done) {
var data = [];
search();
function search(lastId) {
var args = {
screen_name: user,
count: 200,
max_id: lastId
};
twit.getUserTimeline(args, onTimeline);
function onTimeline(err, chunk) {
if (err) {
console.log('Twitter search failed!');
return done(err);
}
if (data.length) chunk.shift(); // What is this for?
data = data.concat(chunk);
var thisId = parseInt(data[data.length - 1].id_str);
if (chunk.length) return search(thisId);
return done(undefined, data);
}
}
}

Resources