Using Promise.all() for multiple http/oauth queries - javascript

I'm trying to wait for the output of two OAuth calls to an API, and I'm having trouble retrieving the data from those calls. If I use Promise.all(call1,call2).then() I am getting information about the request object.
First, here's the setup for the fitbit_oauth object:
var fitbit_oauth = new OAuth.OAuth(
'https://api.fitbit.com/oauth/request_token',
'https://api.fitbit.com/oauth/access_token',
config.fitbitClientKey,
config.fitbitClientSecret,
'1.0',
null,
'HMAC-SHA1'
);
foodpath = 'https://api.fitbit.com/1/user/-/foods/log/date/' + moment().utc().add('ms', user.timezoneOffset).format('YYYY-MM-DD') + '.json';
activitypath = 'https://api.fitbit.com/1/user/-/activities/date/' + moment().utc().add('ms', user.timezoneOffset).format('YYYY-MM-DD') + '.json';
Promise.all([fitbit_oauth.get(foodpath, user.accessToken, user.accessSecret),
fitbit_oauth.get(activitypath, user.accessToken,
user.accessSecret)])
.then(function(arrayOfResults) {
console.log(arrayOfResults);
}
I want arrayOfResults to give me the data from the calls, not information about the requests. What am I doing wrong here? I'm new to promises so I'm sure this is easy for someone who isn't.
The callback for a single fitbit_oauth call is as follows:
fitbit_oauth.get(
'https://api.fitbit.com/1/user/-/activities/date/' + moment().utc().add('ms', user.timezoneOffset).format('YYYY-MM-DD') + '.json',
user.accessToken,
user.accessSecret,
function (err, data, res) {
if (err) {
console.error("Error fetching activity data. ", err);
callback(err);
return;
}
data = JSON.parse(data);
console.log("Fitbit Get Activities", data);
// Update (and return) the user
User.findOneAndUpdate(
{
encodedId: user.encodedId
},
{
stepsToday: data.summary.steps,
stepsGoal: data.goals.steps
},
null,
function(err, user) {
if (err) {
console.error("Error updating user activity.", err);
}
callback(err, user);
}
);
}
);
Thanks to jfriend00 I got this working, here's the new code:
function fitbit_oauth_getP(path, accessToken, accessSecret) {
return new Promise (function(resolve, reject) {
fitbit_oauth.get(path, accessToken, accessSecret, function(err, data, res) {
if (err) {
reject(err);
} else {
resolve(data);
}
}
)
})};
Promise.all([fitbit_oauth_getP(foodpath, user.accessToken, user.accessSecret),
fitbit_oauth_getP(activitypath, user.accessToken, user.accessSecret)])
.then(function(arrayOfResults) {
console.log(arrayOfResults);
});

Promise.all() only works properly with asynchronous functions when those functions return a promise and when the result of that async operation becomes the resolved (or rejected) value of the promise.
There is no magic in Promise.all() that could somehow know when the fitbit functions are done if they don't return a promise.
You can still use Promise.all(), but you need to "promisify" the fitbit functions which is a small wrapper around them that turns their normal callback approach into returning a promise that was then resolved or rejected based on the callback result.
Some references on creating a promisified wrapper:
Wrapping Callback Funtions
How to promisify?
If you have an async function that accepts a callback to provide the async result such as fs.rename(oldPath, newPath, callback), then you can "promisify" it like this:
function renameP(oldPath, newPath) {
return new Promise(function(resolve, reject) {
fs.rename(oldPath, newPath, function(err) {
if (err) {
reject(err);
} else {
resolve();
}
});
});
};
renameP("orig.txt", "backup.txt").then(function() {
// successful here
}, function(err) {
// error here
});
Some promise libraries such as Bluebird have a built in .promisify() method that will do this for you (it will return a function stub that can be called on any function that follows the node.js async calling convention).

Related

Waiting for promise to resolve from parent function

I have a primary thread in my node application such as this:
function main_thread() {
console.log("Starting");
values = get_values(1);
console.log(values);
console.log("I expect to be after the values");
}
The get_values function calls the hgetall function using the node_redis package. This function provides a call back, but can be promisified:
function get_values(customer_id) {
// Uses a callback for result
new Promise(function(resolve, reject) {
redis_client.hgetall(customer_id, function (err, result) {
if (err) console.log(err)
console.log("About to resolve");
resolve(result);
});
})
.then(({result}) => {
console.log(result);
});
}
This works great for promise chaining within the function, however not so well in my main thread, as I can't wait and return the value.
Here's how I'd do it in ruby, the main language I use:
def get_values(customer_id)
return #redis_client.hgetall(customer_id)
end
How can I create a promise within a reusable function and make the main thread wait until the function returns the response from the promise?
EDIT:
It's been suggested the promise can be returned with a then chained in the main thread. However this still means any code in the main thread after after the function call executes before the then block.
EDIT 2:
After lengthy discussion with some IRL JS developer friends, it looks like trying to create a synchronous script is against the ethos of modern JS. I'm going to go back to my application design and work on making it async.
Here is a working example with async/await. I've replaced the redis with a timeout and an array for the data.
async function main_thread() {
console.log("Starting");
values = await get_values(1);
console.log(`After await: ${values}`);
console.log("I expect to be after the values");
}
async function get_values(customer_id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const result = [1, 2, 3];
console.log(`Resolving: ${result}`);
resolve(result);
}, 300);
});
}
main_thread();
Further reading:
Using Promises
Promise Constructor
Return the promise in get_values
function get_values(customer_id) {
// Uses a callback for result
return new Promise(function(resolve, reject) {
redis_client.hgetall(customer_id, function (err, result) {
if (err) console.log(err)
console.log("About to resolve");
resolve(result);
});
})
.then(({result}) => {
reject(result);
});
}
Now in your main thread, you could wait for it like:
function main_thread() {
console.log("Starting");
get_values(1).then(function(values) {
console.log(values);
}).catch(function(error) {
console.error(error);
});
}
Simple as returning the promise (chain) from your function
function get_values(customer_id) {
// Uses a callback for result
return new Promise(function(resolve, reject) {
redis_client.hgetall(customer_id, function (err, result) {
if (err) console.log(err)
console.log("About to resolve");
resolve(result);
});
})
.then(({result}) => {
console.log(result);
});
}
And then in your main async function or function
let result = await get_values(); or get_values.then(function(result){})
function main_thread() {
console.log("Starting");
values = get_values(1).then(function(values){
console.log(values);
console.log("I expect to be after the values");
});
}
async function main_thread() {
console.log("Starting");
let values = await get_values(1);
console.log(values);
console.log("I expect to be after the values");
}

Function returned undefined, expected Promise or value async method

I have an async method named generateSession. And I want the promises system to wait till the call is done. I wait till the data is there and than delete the row at the database. For now, that doesn't make any sense.
But I get an error at this state. Function returned undefined, expected Promise or value It looks like this comes from calling the generateSession. But I don't know how to fix it.
exports.pending = functions.database
.ref('/groups/{groupId}/status/pending/{deviceId}/')
.onCreate(event => {
generateSession().then(function(data) {
console.log("generated session:" + data.sessionId);
return event.data.ref.set({})
}).catch(function(err) {
console.log("error:", err);
});
});
var generateSession = function(){
// *** Return a promise
return new Promise(function(resolve, reject) {
opentok.createSession({mediaMode:"relayed"}, function(err, session){
if (err) {
reject(err);
} else {
resolve(session);
}
});
});
};
The onCreate of Firebase Functions itself needs to return a Promise:
exports.pending = functions.database
.ref('/groups/{groupId}/status/pending/{deviceId}/')
.onCreate(event => {
// You should return the result of generateSession() here
return generateSession().then(function(data) {
console.log("generated session:" + data.sessionId);
return event.data.ref.set({})
}).catch(function(err) {
console.log("error:", err);
// You probably don't want to catch here, let the error
// go through so that Cloud Functions can pick it up
})
});

Javascript Promise prematurely resolving

I have a function that returns a Promise, that accesses the database and pulls a few lines out, assigning them to a Javascript variable.
The issue is that my '.then' clause is being triggered even though I know the Promise hasn't resolved:
app.post("/api/hashtag", function (req, res) {
FindPopularRumours().then(function (resolveVar) {
console.log(resolveVar);
console.log();
res.send(resolveVar);
}).catch(function () {
console.log("DB Error!");
res.send("DB Error!");
});
});
And the Promise function:
function FindPopularRumours() {
return new Promise((resolve, reject) => {
var hashtags = [];
var dbPromise;
db.collection(HASHTAGS).find().forEach(function (doc) {
hashtags.push(doc.hashtag);
console.log(hashtags);
});
resolve(hashtags);
});
}
The result output is:
[ ]
['#test1']
['#test1', '#test2']
['#test1', '#test2', '#test3']
As you can see, the first line ('[ ]') should ONLY be executed AFTER the hashtags have been output. But for some reason my code seems to think the Promise has been resolved before it actually has.
EDIT1
As per Ankit's suggestion, I have amended my function to:
function FindPopularRumours() {
return new Promise((resolve, reject) => {
var hashtags = [];
db.collection(HASHTAGS).find({}, function (err, doc) {
if (!err) {
doc.forEach(function (arg) {
hashtags.push(arg.hashtag);
console.log(hashtags);
});
resolve(hashtags);
} else {
return reject(err);
}
});
});
}
This still returns the same output response as before (e.g the 'then' clause is running before the promise itself).
My POST function is still the same as before.
The db.collection.find() function is async, so you have to resolve the promise inside the callback for that, something like
function FindPopularRumours() {
return db.collection(HASHTAGS).find().toArray().then( (items) => {
return items.map( doc => doc.hashtag);
});
}
takes advantage of the Mongo toArray() method, that returns a promise directly
Please note that db.collection(HASHTAGS).find() is an asynchronous call. So, your promise is resolved before database query returns. To solve this problem, you need to re-write your database query as follows:
function FindPopularRumours() {
return new Promise((resolve, reject) => {
var hashtags = [];
var dbPromise;
db.collection(HASHTAGS).find({}, function(err, doc){
if(!err){
doc.forEach(function (arg) {
hashtags.push(arg.hashtag);
console.log(hashtags);
});
resolve(hashtags);
}else{
return reject(err);
}
});
});
}
Hope the answer helps you!

Node js array dashboard

I'm doing the backend of my app with node js. In this case i'm trying to get the typical dashboard like facebook, instagram,....
Where for one user i'm trying to get the users that he follows. And when i get the array of users following, i find the "recetas" that they have (one user can have more than one). And finally i add all this recetas in an array but the problem is that is returning me empty.
getDashboard = function (req, res) {
var myarray = new Array();
//myarray = [];
User.findById(req.params.id, function (err, user) {
if (!user) {
res.send(404, 'User not found');
}
else {
var a = user.following;
a.forEach(function (current_value) {
Receta.find({ "user_id": current_value._id }, function (err, recetas) {
if (!err) {
recetas.forEach(function (receta) {
myarray.push(receta);
}
} else {
console.log('Error: ' + err);
}
});
})
res.send(myarray);
}
});
};
You are dealing with a common async issue. Receta.find is asynchronous, it is not a blocking operation, so res.send is called before all of your Receta.find calls have completed. You can get around this issue by using Promises, assuming they are available in your version of Node:
var a = user.following;
var promises = a.map(function(current_value) {
return new Promise(function(resolve, reject) {
Receta.find({"user_id":current_value._id}, function (err, recetas) {
if(!err) {
resolve(recetas);
} else {
reject(err);
}
});
});
});
Promise.all(promises).then(function(allData) {
res.send(allData);
}).catch(function(error) {
res.send(error);
});
If native Promises aren't available, you can use a library like Q or bluebird
res.send(myarray); is being called before a.forEach completes due to Receta.find which is I/O.
call res.send only when the loop is finished and recetas returned.

How to use callback or promises in node js? [duplicate]

This question already has answers here:
How do I convert an existing callback API to promises?
(24 answers)
Closed 6 years ago.
Hello I am new to promises and callbacks in node js
I am doing something to fetch users list using another function using callbacks but getting fail.
somewhere I found to use promises. but never used promises.
can anyone help me with code?
send_noti('12', function(res){
console.log(res);
});
function send_noti(value, callback){
connection.query(" SELECT * from users ", function( err, res ){
callback( res );
});
}
You're looking for something like this:
function sendNotiAsync(value) {
return new Promise((resolve, reject) => // Promise constructor
connection.query("SELECT * from users", (err, data) => // Perform the action
err ? reject(err) : resolve(data))); // If error exists, reject, else resolve
}
And use it like this:
sendNotiAsync(someValue)
.then(data => {
// Work with data here, for example
console.log(data);
})
.catch(err => {
// Handle errors here
console.error(err);
});
In node callbacks of async functions should always have the signature function(err, result /*,...*/)
Your example should look like:
send_noti('12', function(err, res) {
if( !err ) {
console.log(res);
}
});
function send_noti(value, callback) {
connection.query(" SELECT * from users ", callback );
}
Beside that your example does not use Promises. With Promises it would look like this:
send_noti('12')
.then(function(res) {
console.dir(res)
})
.catch(function(err) {
console.dir(err)
})
function send_noti(value, callback) {
return new Promise(function(resolve, reject) {
try {
connection.query(" SELECT * from users ", function(err, res) {
if (err) {
reject(err);
} else {
resolve(res);
}
});
} catch (err) {
reject(err);
}
})
}

Categories

Resources