I am trying to create an array called pw_array, assign the contents of pw_subscribers to that array, then append each object in the pw_array with new key value pairs from the second promise. I am new to promises and having a lot of trouble making this work. Right now when I console.log(pw_customer) inside the second promise, inside the getCustomers function, it is returning what I want. But when I console.log(pw_array) later it is the original array.
var pw_array = [];
//first promise is working correctly
var getPaywhirlSubscribers = new Promise(function(resolve, reject) {
paywhirl.Subscriptions.getsubscribers({limit:100}, function(error, pw_subscribers) {
Promise.all(JSON.parse(pw_subscribers).forEach(function(pw_subscriber) {
pw_array.push(pw_subscriber);
}))
// console.log(pw_array);
return resolve(pw_array);
});
});
var getGatewayReferences = function(pw_array) {
return new Promise(function(resolve, reject) {
Promise.all(pw_array.forEach(function(pw_customer) {
paywhirl.Customers.getCustomer(pw_customer.customer_id, function(error, customer) {
pw_customer.phone = customer.phone;
pw_customer.address = customer.address;
pw_customer.gateway_reference = customer.gateway_reference;
// this console.log is returning what I want
// console.log(pw_customer);
});
}));
resolve(pw_array);
// console.log(pw_array);
});
};
and the promise chain...
getPaywhirlSubscribers.then(getGatewayReferences).then(function(pw_array) {
// this console.log is returning the original pw_array with pw_subscribers but not with the appended pw_customer keys
console.log(pw_array);
});
All of your code can be reduced to
var getPaywhirlSubscribers = function() {
return new Promise(function(res, rej) {
paywhirl.Subscriptions.getSubscribers({limit:100}, function(err, subs) {
if (err) {
rej(err);
} else {
res(JSON.parse(subs));
}
});
});
};
var gatewayRefs = function(promiseOfArray) {
return promiseOfArray.then(function(subs) {
return Promise.all(subs.map(function(sub) {
return new Promise(function(res, rej) {
paywhirl.Customers.getCustomer(sub.customer_id, function(err, customer) {
if (err) {
rej(err);
} else {
res(Object.assign({}, sub, customer);
}
});
});
});
});
};
gatewayRefs(getPaywhirlSubscribers()).then(function(arrayOfCustomers) {
// do something with the customer array
});
Note that you can make this even shorter/simpler if you use one of the many utilities available to automatically convert node.js-style error -first callback APIs into Promise-based ones. Search for 'promise denodeify'.
You could also conceivably pull some of the steps out into a .then chain to reduce nesting, but that's as much aesthetic as practical IMHO.
Related
Here is my code.
const fbLoginData= {}
function _responseInfoCallbackID (error, result) {
if (error) {
fbLoginData.error="Unable to fetch data.Try again later or use other methods to sign-in"
}
else {
fbLoginData.id = result.id
fbLoginData.email=result.email
fbLoginData.name=result.name
}
}
function _responseInfoCallbackPicture (error, result) {
if (error) {
fbLoginData.error="Unable to fetch data.Try again later or use other methods to sign-in"
}
else {
fbLoginData.profile= result.data.url
}
}
export async function logInFB () {
try{
const login = await LoginManager.logInWithPermissions(["public_profile","email"])
const value = await getValue(login)
console.warn(" THE VALUE I AM SENDING ",value)
return value
}
catch(error)
{
fbLoginData.error= "Unable to fetch Data"
}
}
const getValue = (result)=> {
if (!result.isCancelled) {
return AccessToken.getCurrentAccessToken().then(
(data) => {
fbLoginData.accessToken = data.accessToken
const infoRequest = new GraphRequest(
'/me',
{
accessToken,
parameters: {
fields: {
string: 'email,name'
}
}
},
_responseInfoCallbackID,
);
const pictureRequest = new GraphRequest(
`/me/${PICTURE_PARAM}`,
{
accessToken,
},
_responseInfoCallbackPicture,
);
// Start the graph request.
new GraphRequestManager().addRequest(infoRequest).start()
new GraphRequestManager().addRequest(pictureRequest).start()
return fbLoginData
}
)
}
}
I want to have fbLoginData object to be filled with the data after having the callback functions _responseInfoCallbackID and _responseInfoCallbackPicture called. But the async function loginFB value doesn't return any other data than access token, but I want id, email, name that gets returned. I know the problem is due to asynchronicity, how can I get the fbLoginData with all the data I need? I cannot figure out the way I can do that. Any help on how to get the loginFB return the fbLoginData value with id,email,name and image that I want ?
This problem can be solved by following below steps:
Wrap the graph request's
new GraphRequestManager().addRequest(callback).start() inside a promise block
Create a global object promiseContainerand add the resolve and reject method of the above created promises to it.
Pass the above promises to Promise.all.
Resolve the promise inside of the callback function by using promiseContainer.
The Promise.all() method returns a single Promise that resolves when all of the promises passed as an iterable have resolved or when the iterable contains no promises.
const fbLoginData = {};
const promiseContainer = {};
function _responseInfoCallbackID(error, result) {
// Earlier Code
promiseContainer.infoPromise.resolve(true);
}
function _responseInfoCallbackPicture(error, result) {
// Earlier Code
promiseContainer.picturePromise.resolve(true);
}
// Earlier Code
const getValue = result => {
if (!result.isCancelled) {
return AccessToken.getCurrentAccessToken().then(data => {
// Earlier code
const infoPromise = new Promise(function(resolve, reject) {
promiseContainer['infoPromise'] = { resolve, reject };
new GraphRequestManager().addRequest(infoRequest).start();
});
const picturePromise = new Promise(function(resolve, reject) {
promiseContainer['picturePromise'] = { resolve, reject };
new GraphRequestManager().addRequest(pictureRequest).start();
});
return Promise.all([infoPromise, picturePromise]).then(() => fbLoginData);
});
}
};
I have a nodejs Mongodb code like this . I'm trying to push into a global array and return all my results but the array contains only one value . Any idea why ?
var s = [];
var p = function(){
return new Promise(function(resolve, reject) {
MongoClient.connect(url, function (err, db) {
db.listCollections().forEach(function(collname) {
db.collection(collname.name).find().sort( { duration_in_seconds: 1 }).limit(1).toArray(
function(err, docs) {
assert.equal(null, err);
s.push(docs);
resolve();
});
});
});
});
}
p().then(function() {
console.log(s);
});
You are resolving the promise when the first collections returns its document. You'd need to wait for all of them. Instead of wrapping everything in a large new Promise, promisify every asynchronous function on its own, and make the returned promise fulfill with the result value instead of putting it in a global variable:
function connect(url) {
return new Promise(function(resolve, reject) {
MongoClient.connect(url, function (err, db) {
if (err) reject(err);
else resolve(db);
});
});
}
function getArray(cursor) {
return new Promise(function(resolve, reject) {
cursor.toArray(function(err, docs) {
if (err) reject(err);
else resolve(docs);
});
});
}
Now you can write your p using these helpers, put the promises for each collection in an array, and await them with Promise.all, which yields a promise right for the array of all results:
function p() {
return connect(url).then(function(db) {
return getArray(db.listCollections()).then(function(collections) {
var promises = collections.map(function(collname) {
return getArray(db.collection(collname.name).find().sort({duration_in_seconds: 1 }).limit(1));
});
return Promise.all(promises);
});
});
}
p().then(function(s) {
console.log(s);
});
I am really stuck with passing values between promises, I wonder if I can get a help here.
I have a CustomerController calling methods in CustomerRepo
let getCustomers = customerRepo.getCustomers(req.query.offset, req.query.return);
let getProfiles = customerRepo.getProfiles(rows);
getCustomers
.then(function (rows) {
console.log(rows[0]);
})
.then(getProfiles)
I do not know how to pass rows (array of customers) to getProfiles so I can populate the customers object with profiles.
Here is the code in CustomerRepo -
var getCustomers = function (offset, _return) {
return new Promise(function (resolve, reject) {
var sqlString = 'call qsel_customers' + '(' + offset + ',' + _return + ')';
pool.getConnection(function (err, connection) {
if (err) {
return reject(err);
}
connection.query(sqlString, function (err, rows) {
if (err) {
reject(err);
}
else
resolve(rows);
connection.release();
});
connection.on('error', function (err) {
res.json({"code": 100, "status": "Error in connection database"});
});
});
});
}
and for getProfiles
var getProfiles = function (rows) {
return new Promise(function (resolve, reject) {
console.log(rows);
}
}
I get rows undefined. Also please could someone suggest how can I extract the db mysql connection code into a dbConnect.js, to avoid code repetition.
Promise denotes the computation that may fail or not at some point in time. Therefore, in order to create a Promise you do:
return new Promise(function(resolve, reject) {
// here you decide when the computation fails and when succeeds
resolve(1) // success
reject('error') // failed
})
Promise has a method (a swiss-knife, indeed) called then. It takes a function ƒ of one argument. Now, the magic of ƒ looks like this:
if ƒ returns simple value (string, number, array, etc) then it will wrap it in a new Promise and return it. The signature is then : Promise[a] -> (a -> b) -> Promise[b]. Therefore, say you have a function ∂ that returns a Promise of a number and ƒ takes a number and adds "!" to it, you and get this number, pass it to ƒ using then and end up with a new Promise:
ƒ.then(n => n + '!') // Promise[String]
if ƒ returns a Promise, then then will "extract" the value if this Promise, pass it to ∂ and return a new Promise with the result. In pseudocode:
ƒ.then(n => Promise(n + '!')) // Promise[String]
Okay, then returns a Promise. Well, let's design the code:
let getCustomers = () => Promise.resolve([ {}, {}, {} ])
// assume myApi.getProfile takes a Customer and returns a Profile
let getProfiles = (customers) => customers.map(myApi.getProfile)
getCustomers().then(getProfiles).then(console.log.bind(console));
I'm no expert in Promises but i don't think that it is good idea to put pool.getConnection inside your getCustomers promise.
It looks like another promise and should be chained rather then combined.
Getting back to your main quersion. You can do this
getCustomers
.then(function (rows) {
return getProfiles(rows);
})
.then(function() {
console.log('get profiles resolved');
});
and
var getProfiles = function (rows) {
return new Promise(function (resolve, reject) {
resolve(rows);
console.log('other log of rows:' + rows);
}
}
EDITED:
let getCustomers = customerRepo.getCustomers(req.query.offset, req.query.return);
let getProfiles = customerRepo.getProfiles;
getCustomers
.then(function (rows) {
return getProfiles(rows);
})
.then(function(rows) {
console.log('rows number ' + rows);
console.log('get profiles resolved');
);
When chaining Promises, you have to return a Promise resolved with the value you're passing from the upstream function in order to use it in the downstream function.
getCustomers
.then(function (rows) {
console.log(rows[0]);
return Promise.resolve(rows[0]);
})
.then(function(firstRow) {
console.log("The first row's id is " + firstRow.id);
return Promise.resolve(firstRow);
})
.then(getProfiles);
If you need to do asynchronous work in one of the functions, return a new Promise and invoke the resolve/reject functions as appropriate in the asynchronous callback. Promise.resolve() is a shortcut you can use if you're not doing anything asynchronously; it simply returns a Promise object resolved with the value it's passed.
I am trying to get all records from a DynamoDB table using promises. The problem is that DynamoDB do not return all items in one call I have to make multiple calls. If LastEvaluatedKey is not null means that I need to make another call with that key to get the remaining records. In my code I am checking that and resolving only after LastEvaluatedKey is null. But the console.log("done") is not being executed.
Here is my code:
function query(params) {
return new Promise(function(resolve, reject) {
docClient.query(params, function(err, data) {
if (err) {
reject(err)
} else {
resolve(data);
}
});
})
}
function getAllRecords(params, combinedData) {
return new Promise(function(resolve, reject) {
query(params)
.then(function(data) {
if(!combinedData) {
combinedData = [];
}
combinedData.push(data.Items);
if(data.LastEvaluatedKey) {
params.ExclusiveStartKey = data.LastEvaluatedKey;
getAllRecords(params, combinedData)
}
else {
resolve(combinedData);
}
})
})
}
getAllRecords(params)
.then(function() {
console.log('done')
})
.catch(function(error) {
console.log(error);
})
It's probably a misconception on how promises work from my part. If someone can give me an idea how to make this work. That would be great.
You've fallen prey to the explicit promise construction antipattern, in which you are manually constructing promises when you don't need to.
Generally the only time you need to use the Promise constructor is when you are converting non-Promise async code to Promise async code. You've already done that in your query() function, so you don't need to use the Promise constructor in your getAllRecords() function.
You should do this instead:
function getAllRecords(params) {
return query(params).then(function (data) {
var items = [data.Items];
if(data.LastEvaluatedKey) {
params.ExclusiveStartKey = data.LastEvaluatedKey;
return getAllRecords(params).then(function (theRest) {
return items.concat(theRest);
});
}
return items;
});
}
I trying to figure out how to correct re-write my function using promises.
The original working version is below:
this.accountsAPI.find(filter, function(err,result){
if (err || 0 == result.length) {
return res.status(400).json({error: "Can't find the account."});
}
var workRequest = req.query.workRequest;
// result has some records and we assume that _id is unique, so it must have one entry in the array
var newJob = { jobId: workRequest, acceptedById: result[0]._id, dateCreated: new Date() };
this.jobsAPI.create( newJob, function(jobErr, jobResult) {
if (jobErr) { return res.status(400).json({error: "Can't create a new job."}); }
res.status(200).json({newJob});
});
});
I have re-written this as:
return new Promise(function ( fulfill, reject) {
this.accountsAPI.find(filter)
.then(function (result) {
if (0 == result.length) { return res.status(400).json({error: "Can't create a new job."}); }
var workRequest = req.query.workRequest;
// result has some records and we assume that _id is unique, so it must have one entry in the array
var newJob = { workRequestId: workRequest, acceptedById: result[0]._id, dateCreated: new Date() };
this.jobsAPI.create( newJob, function(jobErr, jobResult) {
if (jobErr) { return res.status(400).json({error: "Can't create a new job."}); }
res.status(200).json({newJob});
})
})
.catch((err) => {
return res.status(400).json({
error: "Can't create a job.",
errorDetail: err.message
});
});
Not positive that I coded the promise version correctly. However, even if I did, there is still a chained asynchronous request, so my Promise version just makes things more complicated.
Should I use promises for such calls? Is there a way to rewrite my code elegantly?
No, wrapping everything in a Promise constructor doesn't automatically make it work.
You should start by promisifying the asynchronous functions you are using on the lowest level - that is, in your case, accountsAPI.find and this.jobsAPI.create. Only for this you will need the Promise constructor:
function find(api, filter) {
return new Promise(function(resolve, reject) {
api.find(filter, function(err, result) {
if (err) reject(err);
else resolve(result);
});
});
}
function create(api, newJob) {
return new Promise(function(resolve, reject) {
api.create(newJob, function(err, result) {
if (err) reject(err);
else resolve(result);
});
});
}
As you see, this is a bit repetitive, you can write a helper function for this; but in case you are using a promise library that is more than an ES6 polyfill it will probably provide one already.
Now we've got two functions, find and create, that will return promises. And with those, you can rewrite your function to a simple
return find(this.accountsAPI, filter).then(function(result) {
if (result.length == 0)
throw new Error("Can't find the account.");
return result;
}, function(err) {
throw new Error("Can't find the account.");
}).then(function(result) {
// result has some records and we assume that _id is unique, so it must have one entry in the array
return create(this.jobsAPI, {
jobId: req.query.workRequest,
acceptedById: result[0]._id,
dateCreated: new Date()
}).catch(function(err) {
throw new Error("Can't create a new job.");
});
}.bind(this)).then(function(newJob) {
res.status(200).json(newJob);
}, function(err) {
res.status(400).json({error:err.message});
});