I have been reading many posts on how to chain asynchronous functions but I just can't seem to get it right!
As the title indicates. I am trying to chain mongoskin database calls together, so that i can gather all the information in chunks and then finally send the accumulated result in the response.
I have the object user as :
var User = {
username: 'someusername',
accounts: [{name: 'account_1'}, {name: 'account_2'}]
}
For each of the accounts I need to gather data and then send the accumulated data in the response. So i am using the following for loop to iterate over the accounts:
var promise = require('bluebird');
var db = require('mongoskin').db('mongodb://localhost/someDB');
for(var x in user.accounts){
//Fetch account data
user.accounts[x].accountData = fetchAccountData(user.accounts[x].name);
}
//Finally send the collected response
response.send(user);
And the function fetchAccountData looks like the following:
function fetchAccountData(screen_id){
db.collection('master')
.aggregate([
{$match: {screen_id: screen_id}}
], function(err, res){
if(err)
return null;
else{
console.log('Done', screen_id);
return res;
}
});
}
How can i chain this to have the following algorithm:
start:
for each account:
fetchDataForAccount
Finally:
Send Response
Your algorithm can be achieved using the following code:
var Promise = require('bluebird');
var mongo = require('mongoskin'), db;
Promise.promisifyAll(mongo.Collection.prototype);
db = mongo.db('mongodb://localhost/someDB');
Promise.all(user.accounts.map(function(acct) {
return fetchAccountData(acct.name).then(function(data) {
acct.accountData = data;
});
}))
.then(function() {
response.send(user);
})
.catch(function(err) {
// handle error
});
function fetchAccountData(screen_id){
return db
.collection('master')
.aggregateAsync([
{$match: {screen_id: screen_id}}
]);
}
EDIT: Here's a breakdown of the code
The first thing you need to do is ensure that aggregate returns a Promise instead of using a continuation (e.g. callback). You can do this by using bluebird's amazing promisification abilities :) Here we use it on mongo.Collection.prototype so that when collection() is called it will return a promise-capable collection instance. Then we have fetchAccountData return the promise returned by aggregateAsync so the client has a way of knowing when that promise is resolved.
Next, we map over each account in accounts and return a promise which will be fulfilled once the account data is fetched and it has been assigned to the account object. We then use Promise.all which will return a promise that is fulfilled "when all the items in the array are fulfilled" (from the docs).
Finally, we have to use then() to "wait" until the promise returned from all has resolved, and the finally send back the response with the complete user object.
Related
I’m trying to set up a js messenger chatbot linked to a database.
Of course, sometimes, user requests data from the database to the chatbot and I do use promise to gather it. I recently learned about them, so I do only know the basics.
My issue is that when the promise is fulfilled, I not only need to trigger some code (which is done through the then() function) but also to return data to the main module in order to create an answer.
How can I resume my function (in order to reach the return) after the promise is fulfilled ?
I'm probably having a difficult time being understood so this is a sequence diagramm of my chatbot and some bits of my code.
Main process of the database request
app.js
const Database = require("./services/database");
const Message = require("./services/message");
app.get("/endpoint", (req,res)=>{
let message
//Check if the user message request data
if (req == datarequest)
{
message = Database.request();
}
else
{
message = "You did not require any data";
}
Message.sendToUser(message);
});
DBInteractionModule.js
async request()
{
//Creating the promise
let promise = new Promise(
function(){
let connection = db.connect();
return connection.query("SELECT * FROM user;");
}
);
// Only(?) way to use data when it returns
promise.then(function(data)
{
console.log("data requested : "+ data);
});
//I need to use this return in order to give data back to the main app
return promise;
}
Thanks :)
Promises are tricky the first time you use them, but once you get use to them, it's all about waiting then resolving or rejecting the promise.
I'm assuming, db.connect and connection.query both return a promise, so you have to wait for them to fulfill there promises.
// the `async` word before the functions here is important
const request = new Promise(async (resolve, reject) => {
// wait for the db to connect
let connection = await db.connect();
// wait for the db to return it's data
const data = await connection.query("SELECT * FROM user;");
// after waiting, if no error, the data should be available now
console.log('Requested data (WITHIN the promise) : ', data);
//return the data by resolving the main promise
resolve(data)
});
Usage :
request.then(data => {
console.log('Requested data (OUTSIDE the promise) : ', data);
//Process data
});
Or inside another async function :
async function main() {
const data = await request;
console.log('Requested data (OUTSIDE the promise) : ', data);
}
I am trying to return 3 promises from my firebase DB and once all three promises have been fulfilled I basically want to render a new page or do whatever. So I do a Promise.All(...) but my lists are still empty afterwards.
My queries are correct because when I console.log() within each of those functions, I get the objects returned from my DB but my Promise.All isn't waiting for those promises to resolve and instead executes the code within the Promise.All which is returning empty lists.
app.get('...', function (req, res) {
//Return first promise from DB save to zone_obj list
var zone_key = req.params.id;
var zone_obj = [];
firebase.database().ref(...).once('value').then((snapme) => {
zone_obj.push(snapme.val());
});
//Return second promise from DB save to members list
var members = [];
firebase.database().ref(...).on("value", function (snapshott) {
snapshott.forEach((snapper) => {
members.push(snapper.val());
});
});
//Return third promise from DB save to experiences list
var experiences = [];
firebase.database().ref(...).on("value", function (snapshot) {
snapshot.forEach((snap) => {
firebase.database().ref(...).once("value").then((snapit) => {
experiences.push(snapit);
});
});
});
//once all promises have resolved
Promise.all([experiences,zone_obj,members]).then(values => {
console.log(values[0]); //returns []
console.log(values[1]); //returns []
console.log(values[2]); //returns []
});
});
This is not actually firebase's problem. Firebase method .on("value") is actually a listener that will be bound to firebase to get real-time updates and that is not actually a promise and your callback function will be called every time when data on that node is changed. so if you want to save or get data only once use firebase.database().ref(...).set() and firebase.database().ref(...).once() method respectively.
According to firebase docs
On method
on(eventType, callback, cancelCallbackOrContext, context) returns function()
once method
once(eventType, successCallback, failureCallbackOrContext, context) returns firebase.Promise containing any type
So change your code to following
app.get('...', function (req, res) {
var promises = []
//Return first promise from DB save to zone_obj list
promises.push(firebase.database().ref(...).once('value'));
//Return second promise from DB save to members list
promises.push(firebase.database().ref(...).once('value'));
//Return third promise from DB save to experiences list
promises.push(firebase.database().ref(...).once('value'));
//once all promises have resolved
Promise.all(promises).then(values => {
console.log(values[0]); // zone_obj
console.log(values[1]); // members
console.log(values[2]); // experiences
});
});
I am using axios.all to call loop through an array of items and make a get request for each so that I can save their data in the correct order. Right now I have an array of Promises, all of which are being resolved with the correct data, and the callback function for when all of these are done is being triggered.
Now I just need to loop through the Promises and save them their values, but I don't know how to access their values!
let promises = [];
for (let report of response.data.res.body.result) {
let dto2 = {
customerId: state.member.customerId,
reportToken: report.reportToken
}
promises.push(axios.post('http://localhost:3001/api/pullReport', dto2));
}
console.log("promises", promises);
axios.all(promises).then(function() {
console.log("done!");
promises.forEach(function(res) {
console.log("res", res);
// commit('SAVE_REPORT', res.value);
})
// resolve('Reports saved.');
});
Here's what each promise looks like when it is consoled in the forEach loop :
__proto__: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: Object <<<<<<<<<<< NEED THIS
I just need to be able to call the commit('SAVE_REPORT') with the PromiseValue Object, but I don't know what to pass in! I've tried res.value, res.val, res.promiseValue... Is there a secret to this?
axios.all creates a new promise, which will resolve with an array of the results. So to interact with those results, you just need to add a parameter to the function in the .then block:
axios.all(promises).then(function(results) {
results.forEach(function(res) {
commit('SAVE_REPORT', res.value);
});
});
I'm trying to make multiple database queries using bluebird's Promise.each(). The part where I'm stuck is that I'm not able to handle all the rejections (if multiple promises fail). If I do the same thing using Promise.all() it works fine(It will! because in Promise.all() if 1 promise fails the result is rejected too). My question is:
How should I handle rejections in Promise.each()?
function foo(bar){
return new Promise(resolve, reject){
var query = "elect count(*) from students Where 1=1";//DELIBRATE MISTAKE
connection.query(query, function(err, result){
if(err){reject(err)}resolve(result);
})
}
}
function api(req, res){
var tasks = [];
for(var i = 0; i < 10; i++){
tasks.push(foo(bar));
}
Promise.each(tasks).catch(err=>{return err;});
res.send('message')
}
Response:
Unhandled rejection Error: ER_PARSE_ERROR
You're using Bluebird#each method incorrectly. This method do the following:
Iterate over an array, or a promise of an array, which contains promises (or a mix of promises and values) with the given iterator function with the signature (value, index, length) where value is the resolved value of a respective promise in the input array.
So the first parameter must be an array of promises/values, and the second is a callback which accepts three parameters: value, index, length.
Working example:
let queryAsync = Promise.promisify(connection.query, { context: connection });
function foo(bar) {
var query = 'elect count(*) from students Where 1=1'; // DELIBRATE MISTAKE
return queryAsync(query);
}
function api(req, res){
var tasks = [/* TODO: fill array with taskIds or something else*/];
Promise
.each(tasks, task => foo(task))
.then(() => res.send('message'))
.catch(err => {
console.log(err);
res.status(500).send(err);
});
}
In the example above I use Bluebird#promisify method to promisify callback-style connection.query function. Bluebird already presents the promisification functionality, you shouldn't create your own.
I am having a problem trying to make my for loop behave synchronously when I am making an asynchronous call in each iteration of the loop.
Here is my code:
pipeline: function(userContext, options, done){
var orderData = [];
options.data.forEach(function(store, index){
store.orders.forEach(function(order){
shopify.getPipeline(userContext, {'order':order,'storeId':index}, function(result){
var snapshotId = "30775bf1bb854c5d84c9c2af37bc8fb0";
var resourceToQuery = config.structrUrls.getUrl("ConfigurationSnapshot") + '/' + snapshotId ;
var requestOptions = {
method: "GET",
timeout: 8000
};
requestify.request(resourceToQuery, requestOptions)
.then(function(snapshotResult){
result.snapshots = snapshotResult.getBody().result;
result.snapshots.configurationPayload = JSON.parse(snapshotResult.getBody().result.configurationPayload);
orderData.push(result);
})
.catch(function(err){
console.log (err);
done(err);
});
});
});
});
done(null, orderData);
}
I understand the problem here, but do not know how to remedy it. Let me explain the function:
options.data contains an array of stores, and each store contains an array of orders. For each order, I am calling shopify.getPipeline() for pipeline data (this is a synchronous operation), and in the callback I make a requestify request (a node module used for making http requests) for snapshot data, which I want to append to the result before pushing it onto my "orderData" array. When this all completes, I am calling "done", a callback function, with my orderData. As you can see, since requestify calls are asynchronous, done is called before any data is added to the orderData array.
I think I need to use some kind of promise in order to guarantee the result before calling done, but I have been unsuccessful in implementing a promise into this function. In the documentation for q, it seems like the function I would want to use is promise.all(), which 'Returns a promise that is fulfilled with an array containing the fulfillment value of each promise, or is rejected with the same rejection reason as the first promise to be rejected'. I'm failing to see how to translate my forEach loop into an array of promises. I was also looking at the async parallel function and ran into the same problem regarding the array of functions.
Any help is greatly appreciated. Thanks!
To construct an array of Promises for use in Promise.all, you can map across the array of stores, and again across the array of orders, returning a Promise for each order which will be resolved or rejected based on the result of requestify.request. Merging the resulting nested array gives you a single array of promises which can then be passed to Promise.all.
Using your example:
pipeline: function(userContext, options, done){
var nestedPromises = options.data.map.forEach(function(store, index){
return store.orders.map(function(order){
return new Promise(function(resolve, reject){
shopify.getPipeline(userContext, {'order':order,'storeId':index}, function(result){
var snapshotId = "30775bf1bb854c5d84c9c2af37bc8fb0";
var resourceToQuery = config.structrUrls.getUrl("ConfigurationSnapshot") + '/' + snapshotId ;
var requestOptions = {
method: "GET",
timeout: 8000
};
requestify.request(resourceToQuery, requestOptions)
.then(function(snapshotResult){
result.snapshots = snapshotResult.getBody().result;
result.snapshots.configurationPayload = JSON.parse(snapshotResult.getBody().result.configurationPayload);
resolve(result);
})
.catch(function(err){
reject(err);
});
});
});
});
});
// Flatten nested array.
var promises = Array.prototype.concat.apply([], nestedPromises);
Promise.all(promises).then(function(orderData){
done(null, orderData);
}).catch(function(err){
done(err);
});
}