Here's my original code to fetch a user php session which is stored in redis:
var session_obj;
var key = socket.request.headers.cookie.session
session.get('PHPREDIS_SESSION:'+key,function(err,data){
if( err )
{
return console.error(err);
}
if ( !data === false)
{
session_obj = PHPUnserialize.unserializeSession(data);
}
/* ... other functions ... */
})
I would like to rewrite the code with Promise, but I can't get the data returned:
Promise.resolve(session.get('PHPREDIS_SESSION:'+key)).then(function(data){
return data;
}).then(function(session){
console.log(session); // this returns true, but not the session data
session_obj = PHPUnserialize.unserializeSession(session);
}).catch(function(err){
console.log(err);
})
The session returned just boolean true instead of the session data. The original redis get function has two arguments. I assumed the promise just returned the first argument which was err in the original. So I tried
Promise.resolve(session.get('PHPREDIS_SESSION:'+key)).then(function(err,data){
console.log(data) // return undefined
})
but it wasn't working either.
Does anyone know if redis could work with Promise?
You were trying to use Promise.resolve wrong, it expects a Promise and session.get by default doesn't return a Promise. You first need to promisify it. (or promisifyAll)
session.getAsync = Promise.promisify(session.get);
// OR
Promise.promisifyAll(session); //=> `session.getAsync` automatically created
// OR
Promise.promisifyAll(redis); //=> Recursively promisify all functions on entire redis
Then use the new function which returns the promise like you would use a promise, you don't even need to wrap it with Promise.resolve, just this:
session.get('PHPREDIS_SESSION:' + key).
then(function(data) {
// do something with data
}).
catch(function(err) {
// handle error with err
});
Related
I'm faced with a small issue when trying to chain complex function calls with Promises and callbacks.
I have a main function, which calls subroutines. In these routines API calls are made.
For Example:
function handle(){
new Promise(function(resolve, reject){
let result = doAPICall1()
if (result === true) resolve(true);
reject(JSON.stringify(result))
}).then(function(){
let result = doAPICall2()
if (result === true) return true
throw new Error(JSON.stringify(result))
}).catch(error){
console.error(JSON.stringify(error))
}
}
function doAPICall1(){
axios.get('...').then(function(){
return true
}).catch(function(error){
return error
})
}
function doAPICall2(){
axios.get('...').then(function(){
return true
}).catch(function(error){
return error
})
}
But when I execute this example, doAPICall2 will be executed, while doAPICall1 is still running.
It only occures when long running calls are made.
Does anyone can give me a hint? Thank you!
You're overdoing manually a lot of things that Promises already do for you:
axios.get already returns a Promise, so there is no point in return a the response when it resolves and return a false when it rejects. A catch handler at the end of a Promise chain already handles all errors that may arise during the chain, so you don't need to catch every Promise.
I would do something like:
function doAPICall1(){
return axios.get('...');
}
function doAPICall2(){
return axios.get('...');
}
function handle(){
// in case you would use the api calls results.
let firstResult = null;
let secondResult = null;
return doAPICall1()
.then(res => {firstResult = res})
.then(() => doAPICall2())
.then(res => {
secondResult = res;
return []
})
}
I guess you will use the Api calls results for something. With the code above, you could consume the handle() function like follows:
function someSortOfController(){
handle().then(results => {
console.log(results[0]); // first api call result
console.log(results[1]); // second api call result
})
.catch(err => {
// here you will find any error, either it fires from the first api call or from the second.
// there is *almomst* no point on catch before
console.log(err);
})
}
There, you will access any error, either it came from the first api call or the second. (And, due to how Promises work, if the first call fails, the second won't fire).
For more fine grained error control, you may want to catch after every Promise so you can add some extra logs, like:
function doAPICall1(){
return axios.get('...')
.catch(err => {
console.log('the error came from the first call');
throw err;
});
}
function doAPICall2(){
return axios.get('...')
.catch(err => {
console.log('the error came from the second call');
throw err;
});
}
Now, if the first api call fails, everything will work as before (since you're throwing the error again in the catch), but you have more control over error handling (maybe the error returning from API calls is not clear at all and you want this kind of control mechanism).
Disclaimer
This answer doesn't answer why your code acts like it does. However, there are so much things wrong in your code, so I think providing you with an example about using Promises is more valuable.
Don't worry and take some time to understand Promises better. In the example code below, doAPICall function return a Promise which resolves to a value, not the value itself.
function handle() {
doAPICall().then(result => {
//do something with the result
}).catch(error => {
//catch failed API call
console.error(error)
})
}
doAPICall() {
// this returns a Promise
return axios.get(...)
}
function getMentionedUsers(str, next){
var array = getUsernamesFromString(str); //['john','alex','jess'];
if(array.length > 0){
var users = [];
var pending = array.length;
array.forEach(function(username){
getUserByUsername(username).then(function(model){
users.push(model.key);
--pending || next(users); //this is a callback model
});
});
}
};
function getUserByUsername(username){
return admin.database().ref('/users').orderByChild('username').equalTo(username).once('value').then(function(snapshot) {
return snapshot.val(); //this is the firebase example of a promise
});
};
Right now, I'm doing this:
getMentionedUsers(model.body, function(users){
console.log("Mentions", users);
});
However, I'd like to turn getMentionedUsers into a promise. How can I do that? I'm new to Promises
You could use Promise.all and Array#map:
function getMentionedUsers(str) {
return Promise.all(getUsernamesFromString(str).map((username) => {
return getUserByUsername(username).then((model) => model.key);
}));
}
A more readable version broken into two functions:
function getUserKeyByUsername(username) {
return getUserByUsername(username).then((user) => user.key);
}
function getMentionedUsers(str) {
const promises = getUsernamesFromString(str).map(getUserKeyByUsername);
return Promise.all(promises);
}
Use Promise.all.
const getMentionedUsers = str =>
Promise.all(
getUsernamesFromString(str).map(
username => getUserByUsername(username)
.then(model => model.key)
)
);
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
You can have the best of both. If you pass a next function, it will get called with the results. If not, your method will return a promise.
function getMentionedUsers(str, next){
var array = getUsernamesFromString(str); //['john','alex','jess'];
var promise = Promise.resolve([]); // default
var hasNext = typeof next === 'function';
if(array.length > 0){
promise = Promise.all(array.map(function(username){
return getUserByUsername(username);
}));
}
promise = promise.then(models => {
var users = models.map(model => model.key);
if (hasNext) next(null, users);
return users;
});
if (hasNext) promise.catch(next);
else return promise;
};
UPDATE: Though not part of your original question, this is still a good point and worth pointing out. Your existing code is using a non-standard callback technique. The standard callback technique expects an error as the first parameter and results as a second parameter:
next(new Error(...)); //-> when something fails
next(null, results); //-> when something succeeds
As such, I have updated my code to show the "standard" callback behavior alongside promises. Using the hybrid approach above allows for existing code to stay in place while allowing new code to use the new Promise technique. This would be considered a "non-breaking change".
using native ES6 promises, written in a functional style:
// Returns array of usernames
function getUsernamesFromString(str = '') {
return str.split(',').map(s => s.trim())
}
// returns promise of user
function getUserByUserName(username) {
// Lets say this is a slow async function and returns a promise
return Promise.resolve({
id: (Math.random() * 10 ** 10).toFixed(0),
username
});
}
function getMentionedUsers(str) {
return Promise.all(
getUsernamesFromString(str).map(getUserByUserName)
);
}
getMentionedUsers('kai, ava, mia, nova').then(console.log.bind(console))
However, there are also libraries like bluebird that can promisify objects and functions automatically as long as the follow the NODE convention of (err, result) as the callback arguments.
You can also just return a new Promise((resolve, reject) => { /* all your code */ }) and just call resolve(dataToResolveWith) if it succeeds and reject(new Error()) if it fails, but you rarely have to do that and in fact, it's an anti-pattern.
I have started learning promises and I wanted use it with mongoose. I have read that mongoose promises are primitive so I have applied to q by using this code:
var mongoose = require('mongoose');
var q = require('q');
mongoose.Promise = q.Promise;
The I have created my schema and model and try to call functions via promises:
User.findOne({chat_id: 2}).exec().then(
function (obj) {
console.log("SUCCESS");
console.log(obj);
},
function (err) {
console.log("ERROR");
console.log(err);
}
).done();
When I call this code it always calls resolve part and skips reject part. My console shows always function which has SUCCESS line even if I intentionally query for not existing data.
Am I missing something, or mongoose will always use Model.method().exec().then(resolveFunction)?
Not finding something is not an error, it just means you did not find something. See: What is returned from Mongoose query that finds no matches? and likely your problem is related to this Mongoose JS findOne always returns null
Also try this: Note I have not worked with q.Promise, I use bluebird.
User.findOne({chat_id: 2}).exec().then(
function (obj) {
if (obj.chart_id === 2) {
console.log("SUCCESS");
console.log(obj);
} else {
console.log("obj with obj.chart_id == 2 NOT FOUND");
console.log(obj);
}
},
function (err) {
console.log("ERROR");
console.log(err);
}
).done();
I'm trying to chain Promises. Here's the code
crawler.cache(url).then(function (cRes) {
console.log('get from cache');
if(cRes) {
return Promise.resolve(JSON.parse(cRes));
} else {
console.log('no cache');
crawler.db(url).then(function (result) {
console.log('get from db');
return Promise.resolve(JSON.parse(result));
});
}
}).then(function fetch(someResult) {
console.log('fetch data from' + someResult);
});
What I'm hoping is that it would look into the cache first if it couldn't find then it should look in to db. And then pass the result to the last then which to fetch the result.
But here's what printing from my console.
get from cache
no cache
fetch data from null
get from db
First it couldn't find anything in the cache then it's trying to fetch from the database but it didn't wait until getting from the db is done it went on to fetch data.
How do I solve this problem when the last then has to wait for everything above it to finish to get the result then it can do the next operation?
You can just return the promise inside the then continuation, this will cause the parent promise to wait until the returned promise is fulfilled and adopt it's value.
There is also no need to use the Promise.resolve method, you can just return the value directly.
crawler.cache(url).then(function (cRes) {
if (cRes) {
console.log('cache result');
return JSON.parse(cRes);
} else {
console.log('no cache');
return crawler.db(url).then(function (result) {
return JSON.parse(result);
});
}
}).then(function fetch(someResult) {
console.log('fetch data from' + someResult);
});
Bonus:
If you are storing your cache in memory. you probably don't need to return a promise from your crawler.cache function.
OT: I'd recomend you a slightly different approach. Use the rejection-path of the promises to communicate that a Value is not available/couldn't be resolved or provided.
This way it would be more intuitive wether a (possibly falsy) value is provided or, wether we have to resolve that from a different source like the db.
Sth. like (pseudocode):
crawler.cache = function(url){
//you will have to adapt this part to your structure.
//but you get the idea?
return new Promise(function(resolve, reject){
if(url in _cache){
resolve( _cache[url] );
}else{
//you can simply reject() without a reason,
//or you can return an Error-Object, or some message, or whatever ...
reject({ message: "no cache for " + url });
}
});
}
//try to access the value from the cache
crawler.cache(url)
//if the cache fails (to provide you a value),
//try to resolve the value from the db
.catch(() => crawler.db(url).then(saveToCache))
//parse the result, wether it is from the cache or the db
//maybe you want to add a second argument to catch the case where the db also fails?
.then(str => JSON.parse(str));
//maybe you want to add a catch here,
//in case JSON.parse is provided sth false, like a damaged string?
I'm writing an atom-shell app using ReactJs. Therefore I wrote 2 simple node.js-modules. The first fetches an XML api and returns an array of objects. The second is a simple datastore, which should save the fetched dataset. Both modules are async utilizing bluebird.
Now I have the following scenario:
var Promise = require('bluebird');
store.get()
.tap(print) //#1: []
.then(function (data) {
if (data.length) {
return Promise.resolve(data); //#6: ? also true in the second run ?
} else {
return xmlApi.get()
.tap(print) //#2: [...]
.then(store.insert) // <-- this returns an array with inserted indices
.tap(print) //#3: [...]
-then(store.write) // <-- this returns all stored objects as array
.tap(print) //#4: true <-- ? this is, what I doesn't understand... ?
}
})
.tap(print) //#5: true
.then(function (data) {
//Under normal circumstances, I would set ReactJs state from data array...
})
.catch(handleError)
.done();
My problem is to understand, why beyond step #3 everything resolves to true instead of arrays with values? When testing the libraries in pure node, everything is fine. But also in the second run, when the store holds data, the promise resolves to true...
UPDATE:
store.write
var write = Promise.promisify(require('fs').writeFile)
Store.prototype.write = function () {
return write(this.filename, JSON.stringify(this.data))
.bind(this)
.then(function () {
return Promise.resolve(this.data);
});
};