Correct chained promise syntax - javascript

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});
});

Related

JavaScript promise not firing in order

I have been trying to effectively manage how I build my promises in my Express.js app.
Right now I have the following scenario: During the signup process, a user has an optional Organization Name field. If this is filled in, I need to create the Organization object and then add the _id of it to the other information that I am applying to the user. If there is no Organization name, proceed and update the basic user info.
<-- Right now the user is being updated before the organization is being created. -->
//basic info passed from the signup form
info['first_name'] = req.body.first_name;
info['last_name'] = req.body.last_name;
info['flags.new_user'] = false;
//if organization name is passed, create object and store _id in user object.
let create_organization = new Promise(function(resolve, reject) {
if (req.body.organization_name !== "") { //check if name is sent from form
Organization.create({
name: req.body.organization_name
}, function(err, result) {
console.log(result);
if (!err) {
info['local.organization'] = result._id;
resolve()
} else {
reject()
}
})
} else {
resolve()
}
});
let update_user = new Promise(function(resolve, reject) {
User.update({
_id: req.user._id
}, info, function(err, result) {
if (!err) {
console.log("Updated User!"); < --prints before Organization is created
resolve();
} else {
reject();
}
})
});
create_organization
.then(function() {
return update_user;
})
.then(function() {
res.redirect('/dash');
})
Nothing in your code waits for the first promise to settle before proceeding with starting the subsequent work. The work is started as soon as you call User.update, which is done synchronously when you call new Promise with that code in the promise executor.
Instead, wait to do that until the previous promise resolves. I'd do it by wrapping those functions in reusable promise-enabled wrappers (createOrganization and updateUser):
// Reusable promise-enabled wrappers
function createOrganization(name) {
return new Promise(function(resolve, reject) {
Organization.create({name: name}, function(err, result) {
console.log(result);
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
}
function updateUser(id, info) {
return new Promise(function(resolve, reject) {
User.update({_id: id}, info, function(err, result) {
if (err) {
reject(err);
} else {
resolve();
}
})
});
}
(You may be able to use util.promisify or the promisify npm module to avoid doing that manually.)
And then:
//basic info passed from the signup form
info['first_name'] = req.body.first_name;
info['last_name'] = req.body.last_name;
info['flags.new_user'] = false;
//if organization name is passed, create object and store _id in user object.
(req.body.organization_name === "" ? Promise.resolve() : createOrganization(req.body.organization_name))
.then(function() {
return updateUser(req.user._id, info);
})
.catch(function(error) {
// handle/report error
});
(I stuck to ES5-level syntax since your code seemed to be doing so...)
See, so called 'executor function', passed into Promise constructor, is invoked immediately. That's why you essentially have a race condition here between two remote procedure calls. To solve this, make the update responsible for promise creation instead:
function updateUser(userId, userInfo) {
return new Promise(function(resolve, reject) {
User.update({_id: userId}, userInfo, function(err, result) {
if (err) {
reject(err);
}
else {
resolve(result);
}
});
});
}
... and call this function in then(). By doing this, an executor function will be called only when updateUser is invoked - and that'll be after createOrganization() finished its job.

Get object array promise, then iterate over objects

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.

Array .push is not working inside a promise function

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);
});

Promises not waiting for each other in my nodejs controller

I got 4 promises here, and I thought that it would run the first one, then wait until its finished, THEN run the next one, wait till finished and THEN run the next one etc..
But what happens here is that it runs all of them all at once and does not wait for anything to finish.
This is my promise chain:
//
// Run the promises
//
findBanks
.then(findReceipts)
.then(findExpenses)
.then(sendResult)
.catch(err => {
console.error(err);
console.log("getbankAccountReport ERR: " + err);
res.json({error:true,err})
});
This is the output from my console.log
=====findAllBank=====
=====findAllReceipt=====
=====findAllExpense=====
=====RESOLVE findAllBank=====
=====sendResult=====
=====RESOLVE sendResult=====
=====RESOLVE findAllReceipt=====
=====RESOLVE findAllExpense=====
Am I not understanding promises correct or?
Anyway here is my nodejs controller:
exports.getBankAccountReport = function(req, res) {
//
// Find all bank accounts
//
var bankModel = require('../models/bankModel');
var bankTable = mongoose.model('bankModel');
var bankArray = [];
var findAllBank = new Promise(
(resolve, reject) => {
console.log("=====findAllBank=====")
bankTable.aggregate([
...lots of mongo stuff...
],function(err, data) {
if (!err) {
bankArray = data;
console.log("=====RESOLVE findAllBank=====")
resolve(data);
} else {
reject(new Error('findBank ERROR : ' + err));
}
});
});
//
// Find the RECEIPT for each bank account
//
var receiptModel = require('../models/receiptModel');
var receiptTable = mongoose.model('receiptModel');
var receiptArray = [];
var findAllReceipt = new Promise(
(resolve, reject) => {
console.log("=====findAllReceipt=====")
receiptTable.aggregate([
...lots of mongo stuff...
], function (err, data) {
if (!err) {
receiptArray = data;
console.log("=====RESOLVE findAllReceipt=====")
resolve(data);
} else {
reject(new Error('findReceipts ERROR : ' + err));
}
});
});
//
// Find the EXPENSE for each bank account
//
var expenseModel = require('../models/expenseModel');
var expenseTable = mongoose.model('expenseModel');
var expenseArray = [];
var findAllExpense = new Promise(
(resolve, reject) => {
console.log("=====findAllExpense=====")
expenseTable.aggregate([
...lots of mongo stuff...
], function (err, data) {
if (!err) {
expenseArray = data;
console.log("=====RESOLVE findAllExpense=====")
resolve(data);
} else {
reject(new Error('findExpense ERROR : ' + err));
}
});
});
var sendResult = function(data) {
var promise = new Promise(function(resolve, reject){
console.log("=====sendResult=====")
res.json({error:false,
"bank":bankArray,
"receipt":receiptArray,
"expense":expenseArray})
console.log("=====RESOLVE sendResult=====")
resolve();
});
return promise;
};
//
// Run the promises
//
findAllBank
.then(findAllReceipt)
.then(findAllExpense)
.then(sendResult)
.catch(err => {
console.error(err);
console.log("getbankAccountReport ERR: " + err);
res.json({error:true,err})
});
}
You need to wrap your Promises in functions
var findAllBank = function() {
return new Promise(
(resolve, reject) => {
console.log("=====findAllBank=====")
bankTable.aggregate([
...lots of mongo stuff...
],function(err, data) {
if (!err) {
bankArray = data;
console.log("=====RESOLVE findAllBank=====")
resolve(data);
} else {
reject(new Error('findBank ERROR : ' + err));
}
});
});
});
When resolved, the next function in the chain will be called with the data passed in the resolve() function.
Do no confuse the Promise and the function that builds it
When you create a new Promise(executor), you instanciate a new object that will have two methods (functions of an object), .then(resolveCB [, rejectCB]) and .catch(rejectCB).
The aime is to know, whenever a process is done, if it was successful or if it failed, and the continue accordingly.
var myFirstPromise = new Promise(function executor(resolve, reject) { resolve('resolved'); });
In other words, those methods are used to continue your process once the promise defined by executor is settled. It can either get fulfilled and call the resolveCB callback (usingthen), or rejected and call the rejectCBcallback (using both then and catch). A callback (resolveCB or rejectCB) is a function, not a Promise itself, even if the callback might return a Promise.
myFirstPromise
.then(function resolveCB(result) { console.log(result); }) //you can use a 2nd callback for rejection at this point
.catch(function rejectCB(err) { console.log(err); });
myFirstPromise
.then(
function resolveCB(result) { console.log(result); } // if resolved
, function rejectCB(err) { console.log(err); } // if rejected
)
.catch(function rejectCB(err) { console.log(err); }); // NEVER forget the last catch, just my 2cents :)
We saw the inputs of .then() and .catch() but what about their return value? The both of them will return a new Promise. That's why you can chain the .then()'s and .catch()'s.
myFirstPromise
.then(function resolveCB1(result) { console.log(result); })
.then(function resolveCB2(result) { console.log(result); }) // console.log is called but result is undefined
.catch(function rejectCB1(err) { console.log(err); });
myFirstPromise
.then(function resolveCB3(result) {
throw 'I threw an exception'; // an exception is thrown somewhere in the code
console.log(result);
})
.then(function resolveCB4(result) { console.log(result); })
.catch(function rejectCB2(err) { console.log(err); }); // a promise in the chain get rejected or error occured
In the previous example, we see that our second .then() is hit but result is undefined. The promise returned by first .then() fullfiled but no value as been passed by the executor to the resolve callback resolveCB2. In the second case, an exception occured in resolveCB3, it gets rejected so rejectCB2 is called. If we want our resolve callbacks to receive an argument, we have to notify the exector. To do so, the simplest way is for the callbacks to return a value:
myFirstPromise
.then(function resolveCB1(result) {
console.log(result);
result += ' again';
return result;
})
.then(function resolveCB2(result) {
console.log(result);
return result;
})
.catch(function rejectCB1(err) { console.log(err); });
Now that's said, you've got all the pieces together for understanding a Promise. Let's try to sum it up in a cleaner way:
var myFirstPromise = new Promise(function executor(resolve, reject) { resolve('resolved'); })
, resolveCB = function resolveCB(result) {
console.log(result);
result += ' again';
return result;
}
, resolveLastCB = function resolveLastCB(result) {
console.log(result);
result += ' and again';
return result;
}
, justLog = function justLog(result) {
console.log(result);
return result;
}
;
myFirstPromise
.then(resolveCB)
.then(resolveLastCB)
.then(justLog)
.catch(justLog);
You can now chan them nicely, it's cool and all
myFirstPromise
.then(resolveCB)
.then(resolveCB)
.then(resolveCB)
.then(resolveCB)
.then(resolveCB)
.then(resolveCB)
.then(resolveLastCB)
.then(justLog)
.catch(justLog);
But what if your Promise chain 'really' changes and you need to get rid of myFirstPromise and start with resolveCB instead ? It's just a function, it can be executed but doesn't have any .then() or .catch() method. It's not a Promise. You can't do resolveCB.then(resolveLastCB), it will thow an error resolveCB.then( is not a function or something similar. You might think this is a gross mistake, I didn't call resolveCB and resolveCB().then(resolveLastCB) should work? Unfortunalty for those who thought about that, it's still wrong. resolveCB returns a string, some characters, not a Promise.
In order to avoid this kind of maintenance issue, you should know that the resolve and reject callbacks can return a Promise instead of a value. To do so, we'll use what's called the factory pattern. In simple words, the factory pattern is about instanciating new object using a (static) function instead of using the constructor directly.
var myFirstPromiseFactory = function myFirstPromiseFactory() {
/*
return new Promise(function executor(resolve, reject) {
resolve('resolved');
});
if you just need to resolve a Promise, this is a quicker way
*/
return Promise.resolve('resolved');
}
, resolveFactory = function resolveFactory(result) {
return new Promise(function executor(resolve, reject) {
result = result || 'I started the chain';
console.log(result);
result += ' again';
return resolve(result); // you can avoid the return keyword if you want, I use it as a matter of readability
})
}
, resolveLastFactory = function resolveLastFactory(result) {
return new Promise(function executor(resolve, reject) {
console.log(result);
result += ' and again';
return resolve(result);
});
}
, justLogFactory = function justLogFactory(result) {
return new Promise(function executor(resolve, reject) {
console.log(result);
return resolve(result);
});
}
;
myFirstPromiseFactory() //!\\ notice I call the function so it returns a new Promise, previously we started directly with a Promise
.then(resolveFactory)
.then(resolveLastFactory)
.then(justLogFactory)
.catch(justLogFactory);
// Now you can switch easily, just call the first one
resolveFactory()
.then(resolveLastFactory)
.then(justLogFactory)
.catch(justLogFactory);
justLogFactory('I understand Javascript')
.then(resolveLastFactory)
.then(justLogFactory)
.catch(justLogFactory);
Factory functions might come handy when iterating over an array. It might be used to produce an array of promise given an input:
var myFirstPromiseFactory = function myFirstPromiseFactory() {
/*
return new Promise(function executor(resolve, reject) {
resolve('resolved');
});
if you just need to resolve a Promise, this is a quicker way
*/
return Promise.resolve('resolved');
}
, resolveFactory = function resolveFactory(result) {
return new Promise(function executor(resolve, reject) {
result = result || 'I started the chain';
console.log(result);
result += ' again';
return resolve(result); // you can avoid the return keyword if you want, I use it as a matter of readability
})
}
, resolveLastFactory = function resolveLastFactory(result) {
return new Promise(function executor(resolve, reject) {
console.log(result);
result += ' and again';
return resolve(result);
});
}
, justLogFactory = function justLogFactory(result) {
return new Promise(function executor(resolve, reject) {
console.log(result);
return resolve(result);
});
}
, concatValues = function concatValues(values) {
return Promise.resolve(values.join(' '));
}
, someInputs = [
'I am an input'
, 'I am a second input'
, 'I am a third input'
, 'I am yet an other input'
]
;
myFirstPromiseFactory() //!\\ notice I call the function so it returns a new Promise, previously we started directly with a Promise
.then(resolveFactory)
.then(resolveLastFactory)
.then(justLogFactory)
.catch(justLogFactory);
// Now you can switch easily, just call the first one
resolveFactory()
.then(resolveLastFactory)
.then(justLogFactory)
.catch(justLogFactory);
justLogFactory('I understand Javascript')
.then(resolveLastFactory)
.then(justLogFactory)
.catch(justLogFactory);
// Using a factory functions to create an array of promise usable with Promise.all()
var promiseArray = someInputs.map(function(input) {
return justLogFactory(input);
});
Promise.all(promiseArray)
.then(concatValues)
.then(resolveLastFactory)
.then(justLogFactory)
.catch(justLogFactory);

Bluebird promise loop

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;
});
}

Categories