I know that best way to chain promises in Nodejs/Express is like:
doSomeThing()
.then()
.then()
.catch();
But recently had to use the async and q module to iterate over a list/array and run an async function. I wanted to know that is there better way of doing/writing this -
var deferred = Q.defer();
var deferred2 = Q.defer();
models.Local.findOne({
where: {
id: parseInt(req.body.localid)
}
})
.then(function(resultLocal){
if(!resultLocal){
return res.status(404).json(
{
"status" : "error",
'error': "Local Not Found"
});
}
return models.Documents.create(req.body.document);
})
.then(function(docCreated){
var attributes = req.body.document.Attributes;
async.each(attributes, function(item, callback) {
models.Doc_Tags.create({
value: item.value,
attribute_id: item.id,
document_id: docCreated.id
})
.then(function(attributeCreated){
var upObj = {};
upObj[item.col_name] = item.value;
models[item.table_name].update(upObj,{
where:{
id: req.body.document.local_id
}
})
.then(function(primaryUpdated){
deferred2.resolve();
})
.catch(function(error){
return res.status(400).json({status: 'error', error:error.message});
});
deferred2.promise
.then(function(){
callback();
})
.catch(function(error){
return res.status(400).json({status: "error", error: error.message});
});
})
.catch(function(error){
return res.status(400).json({status: 'error', error:error.message});
});
}, function(err,r){
if( err ) {
return res.status(400).json({status: 'error', error:err.message});
} else {
console.log('All attributes Associated');
deferred.resolve(docCreated);
}
});
deferred.promise.then(function(result, attributes){
var obj = req.body.Local;
models.Local.update(obj, {
where: {
id: result.local_id
}
})
.then(function(resultUpdate){
return res.status(201).json({status: "success", document: result});
})
.catch(function(error){
return res.status(400).json({status: "error", error: error.message});
});
})
.catch(function(error){
return res.status(400).json({status: "error", error: error.message});
});
})
.catch(function(error){
return res.status(400).json({status: "error", error: error.message});
});
Please correct me if I am doing something wrong. Functionality wise the code is running properly but I think I can refactor it somehow to look and read better.
Thanks.
your code can be cleaner and shorter.
basic ideas are
turn callback to promise, e.g., promisify() of bluebird.js can do that
async.each part can be refactor to Promise.all to call promise parallelly
re-arrange .then chain
javascript es6 is cleaner than older version
sample refactored version
const Promise = require('bluebird')
// CustomError should be separated to another node module
class CustomError {
constructor(message, code) {
this.code = code
this.message = message
}
}
let docCreated = undefined
function getPromiseParams(item) {
return Promise.try(() => {
return models.Doc_Tags.create({
value: item.value,
attribute_id: item.id,
document_id: docCreated.id
})
}).then(attributeCreated => {
const upObj = {};
upObj[item.col_name] = item.value;
return models[item.table_name].update(upObj, { where:{ id: req.body.document.local_id } })
}).then(primaryUpdated => {
return docCreated
}).catch(error => {
throw new CustomError(error.message, 400)
})
}
Promise.try(() => {
return models.Local.findOne({ where: { id: parseInt(req.body.localid) } })
}).then(resultLocal => {
if(!resultLocal) throw new CustomError('Local Not Found', 404)
return models.Documents.create(req.body.document)
}).then(_docCreated => {
docCreated = _docCreated // assign value to docCreated
const attributes = req.body.document.Attributes
const promiseParams = attributes.map(item => getPromiseParams(item))
return Promise.all(promiseParams)
}).then(() => {
const obj = req.body.Local
return models.Local.update(obj, { where: { id: result.local_id }})
}).then(() => {
return res.status(201).json({status: "success", document: docCreated})
}).catch(error => {
return res.status(error.code || 400).json({status: "error", error: error.message});
})
Related
I am working on a MERN stack project. I saw that some functions in server side related to fetching or saving data in mongodb have lot of callbacks. In order to avoid callbacks hell, now I am trying promises based solution. However I have encountered issue with this promised based solution. I have a condition and depending on its value I want to either proceed further or just return response and stop.
const mId = req.body.mId;
const cId = req.body.cId;
const lId = req.body.lId;
LP.findOne({
'userId': lId,
'courseId': cId
})
.then( lP => {
return lP.materials;
})
.then( materials => {
if( materials.includes(mId) ) {
console.log('A')
return res.json({'status': 'success'})
}
else {
materials.push(materialId)
return LP.findOneAndUpdate({
'userId': learnerId,
'courseId': courseId
}, {
$set: { materials: materials}
}, {
new: true
})
}
})
.then( update => {
console.log('B')
return res.json({ 'status': 'success' })
})
.catch( err => {
return res.json({ 'status': 'fail' })
})
In above code after A is printed B is also printed and further code is executed which gives me: Cannot set headers after they are sent to the client error. I think that is how promises are supposed to work. But what are the possible solution to avoid this problem. How to return res early on and do not execute code further.
Thanks
Just dont return to stop the chain:
const mId = req.body.mId;
const cId = req.body.cId;
const lId = req.body.lId;
LP.findOne({
'userId': lId,
'courseId': cId
})
.then( lP => {
return lP.materials;
})
.then( materials => {
if( materials.includes(mId) ) {
console.log('A')
res.json({'status': 'success'})
}
else {
materials.push(materialId)
return LP.findOneAndUpdate({
'userId': learnerId,
'courseId': courseId
}, {
$set: { materials: materials}
}, {
new: true
})
}
})
.then( update => {
console.log('B')
res.json({ 'status': 'success' })
})
.catch( err => {
res.json({ 'status': 'fail' })
})
I have the following two functions one calling the other but the record variable is undefined followed by errors. I can't figure out why the script doesn't wait. It seems to just proceed with the undefined variable.
async function searchRecord(recordID) {
client.search({
index: 'records',
type: 'record',
body: {
query: { match: { _id: recordID } }
}
}).then(result => {
return result
}).catch(error => {
console.log(error)
return []
})
}
function test(jsonRecord) {
const userID = jsonRecord.users[0]
searchRecord(jsonRecord.objectID).then(record => {
if (record.length === 0) {
record = jsonRecord
}
})
}
The error that I get is:
UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'length' of undefined
This is asynchronous, try using await.
async function searchRecord(recordID) {
try {
const result = await client.search({
index: 'records',
type: 'record',
body: {
query: {
match: { _id: recordID }
}
}
});
return result;
} catch (error) {
console.log(error);
return [];
}
}
Try updating searchRecord to return:
async function searchRecord(recordID) {
return client
.search({
index: "records",
type: "record",
body: {
query: {
match: { _id: recordID },
},
},
})
.then((result) => {
return result;
})
.catch((error) => {
console.log(error);
return [];
});
}
The function client.search() returns a promise. You could choose to return that promise as is from searchRecord(). And then, handle the catch in your test() function.
Alternatively you could also handle the error inside searchRecord() as well by implementing a try catch block. But the key in this case is to wait for client.search() to finish before returning from searchRecord().
function searchRecord(recordID) {
return client.search({
index: 'records',
type: 'record',
body: {
query: { match: { _id: recordID } }
}
});
}
function test(jsonRecord) {
const userID = jsonRecord.users[0]
searchRecord(jsonRecord.objectID).then(record => {
if (record.length === 0) {
record = jsonRecord
}
}).catch(error => {
console.log(error)
return []
})
}
The error that I get is: UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'length' of undefined
The reason for this is that searchRecord() returns promise that immediately resolves to undefined. There is no return statement in the function searchRecord().
why you not using Promise? it's ok if you want to use async-await like answers above but using Promise its became very easy
function searchRecord (recordID) {
return new Promise((resolve, reject)=>{
client.search({
index: 'records',
type: 'record',
body: {
query: {
match: { _id: recordID }
}
}
}).then(
result => resolve(result)
).catch(
error => {console.log(error);reject());
});
}
function test (jsonRecord) {
const userID = jsonRecord.users[0]
searchRecord(jsonRecord.objectID)
.then(
record => {
if (record.length === 0) {
record = jsonRecord
}
}
)
}
nodejs response returning null array.
how to return a response after map function done
var follow = [];
User.findOne(
{ _id: id },
).then(user => {
user.following.map(results => {
User.find({ _id: results.user })
.exec()
.then(res => {
follow.push(res);
});
});
res.json({
status: true,
follow // returning null array
});
});
};
You need to collect the promises and use Promise.all() to know when they are all done:
User.findOne({ _id: id }).then(user => {
let promises = user.following.map(results => {
return User.find({ _id: results.user })
.exec()
.then(res => {
return res;
});
});
Promise.all(promises).then(follow => {
res.json({
status: true,
follow // returning null array
});
}).catch(err => {
console.log(err);
res.sendStatus(500);
});
});
Note, there is no reason in your original code to use .map() if you weren't going to return anything from the .map() callback. In my code, I return the promise.
I have tried to fix the nesting problems but nothing I have used works, not even Google Cloud Functions - warning Avoid nesting promises promise/no-nesting.
How can I restructure this method? Below is the code.
exports.payout = functions.https.onRequest((request, response) => {
var uid = "nYIAHSYimJMHbMkXqDt9PQ0U3Nf2";
getPayoutsPending(uid).then((array) => {
getPayoutsAmount(array).then((value) => { **// avoid nesting promises**
var valueTrunc = parseFloat(Math.round(value * 100) / 100).toFixed(2);
const sender_batch_id = Math.random().toString(36).substring(9);
const sync_mode = 'false';
const payReq = JSON.stringify({
sender_batch_header: {
sender_batch_id: sender_batch_id,
email_subject: "You have a payment"
},
items: [
{
recipient_type: "EMAIL",
amount: {
value: valueTrunc,
currency: "CAD"
},
receiver: "me#gmail.com",
note: "Thank you.",
sender_item_id: "Payment"
}
]
});
paypal.payout.create(payReq, sync_mode, (error, payout) => {
if (error) {
console.warn(error.response);
response.status('500').end();
throw error;
} else {
console.info("payout created");
console.info(payout);
**// avoid nesting problems**
updatePaymentsPending(uid, sender_batch_id).then(() => {
response.status('200').end();
return;
}).catch((error) => {
return console.error(error);
})
}
});
return null;
}).catch((error) => {
return console.error(error);
})
return null;
}).catch((error) => {
return console.error(error);
})
});
The lines marked // avoid nesting promises are the problems.
EDIT - Result from Answer
line 111:20 reads:
return paypal.payout.create(payReq, sync_mode, (error, payout) => {
line 120:21 reads:
}).then(() => {
EDIT #2
After changing the code to what #imjared provided, i am getting the following errors:
ReferenceError: sender_batch_id is not defined
at exports.payout.functions.https.onRequest (/user_code/index.js:136:40)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/providers/https.js:57:9)
at /var/tmp/worker/worker.js:689:7
at /var/tmp/worker/worker.js:673:9
at _combinedTickCallback (internal/process/next_tick.js:73:7)
at process._tickDomainCallback (internal/process/next_tick.js:128:9)
then:
Function execution took 1327 ms, finished with status: 'crash'
then:
ReferenceError: paymentRequest is not defined
at Promise (/user_code/index.js:111:17)
at buildPaymentRequest (/user_code/index.js:90:14)
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
EDIT #3 - response from destenson post
Code I have:
exports.payout = functions.https.onRequest((request, response) => {
return getPayoutsPending(request.body.uid)
.then(array => getPayoutsAmount(array))
.then(value => {
var valueTrunc = parseFloat(Math.round(value * 100) / 100).toFixed(2);
const sender_batch_id = Math.random().toString(36).substring(9);
const sync_mode = 'false';
const payReq = JSON.stringify({
sender_batch_header: {
sender_batch_id: sender_batch_id,
email_subject: "You have a payment"
},
items: [
{
recipient_type: "EMAIL",
amount: {
value: valueTrunc,
currency: "CAD"
},
receiver: request.body.email,
note: "Thank you.",
sender_item_id: "Payment"
}
]
});
return paypal.payout.create(payReq, sync_mode, (error, payout) => {
if (error) {
console.warn(error.response);
response.status('500').end();
throw error;
}
console.info("payout created");
console.info(payout);
return updatePaymentsPending(request.body.uid, sender_batch_id)
}).then(() => {
response.status('200').end();
return null;
});
})
.catch(error => {
console.error(error);
});
});
When app is executed, the functions logs show this:
TypeError: Cannot read property 'then' of undefined
at getPayoutsPending.then.then.value (/user_code/index.js:120:15)
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
then:
{ batch_header:
{ payout_batch_id: '*************',
batch_status: 'PENDING',
sender_batch_header:
{ sender_batch_id: '************',
email_subject: 'You have a payment' } },
links:
[ { href: 'https://api.sandbox.paypal.com/v1/payments/payouts/*******',
rel: 'self',
method: 'GET',
encType: 'application/json' } ],
httpStatusCode: 201 }
then:
uncaught exception
then:
ReferenceError: uid is not defined
at paypal.payout.create (/user_code/index.js:119:46)
at IncomingMessage.<anonymous> (/user_code/node_modules/paypal-rest-sdk/lib/client.js:140:13)
at emitNone (events.js:91:20)
at IncomingMessage.emit (events.js:185:7)
at endReadableNT (_stream_readable.js:974:12)
at _combinedTickCallback (internal/process/next_tick.js:80:11)
at process._tickDomainCallback (internal/process/next_tick.js:128:9)
lastly:
Function execution took 1517 ms, finished with status: 'crash'
EDIT #4 - final result
After executing application, the following log from function is:
I would solve this problem by chaining the promises, instead of nesting them. When you return a value from a then() callback, it becomes a new promise that can be then be used again.
I have not tested this modified version of your code, but I hope you get the gist of it:
exports.payout = functions.https.onRequest((request, response) => {
var uid = "nYIAHSYimJMHbMkXqDt9PQ0U3Nf2";
return getPayoutsPending(uid)
.then(array => getPayoutsAmount(array))
.then(value => {
var valueTrunc = parseFloat(Math.round(value * 100) / 100).toFixed(2);
const sender_batch_id = Math.random().toString(36).substring(9);
const sync_mode = 'false';
const payReq = JSON.stringify({
sender_batch_header: {
sender_batch_id: sender_batch_id,
email_subject: "You have a payment"
},
items: [
{
recipient_type: "EMAIL",
amount: {
value: valueTrunc,
currency: "CAD"
},
receiver: "me#gmail.com",
note: "Thank you.",
sender_item_id: "Payment"
}
]
});
return paypal.payout.create(payReq, sync_mode, (error, payout) => {
if (error) {
console.warn(error.response);
response.status('500').end();
throw error;
}
console.info("payout created");
console.info(payout);
return updatePaymentsPending(uid, sender_batch_id)
});
}).then(() => {
response.status('200').end();
return null;
}).catch(error => {
console.error(error);
});
});
I hope this helps.
EDIT: the successful case was missing a return null. I guess your linter is picky about that.
EDIT: un-nested last then().
Also untested but it seems like the goal based on your eslint is to un-nest everything. This gets kind of cumbersome but it's doable, I guess.
exports.payout = functions.https.onRequest((request, response) => {
var uid = "nYIAHSYimJMHbMkXqDt9PQ0U3Nf2";
// Returns paymentRequest
const buildPaymentRequest = (value) => {
return new Promise((resolve) => {
var valueTrunc = parseFloat(Math.round(value * 100) / 100).toFixed(2);
const sender_batch_id = Math.random().toString(36).substring(9);
const sync_mode = 'false';
const payReq = JSON.stringify({
sender_batch_header: {
sender_batch_id: sender_batch_id,
email_subject: "You have a payment"
},
items: [{
recipient_type: "EMAIL",
amount: {
value: valueTrunc,
currency: "CAD"
},
receiver: "me#gmail.com",
note: "Thank you.",
sender_item_id: "Payment"
}]
});
resolve(paymentRequest);
});
}
// Returns payout
const createPayout = (paymentRequest) => {
return new Promise((resolve, reject) => {
paypal
.payout
.create(payReq, sync_mode, (error, payout) => {
if (error) {
console.warn(error.response);
reject(error);
} else {
console.info("payout created");
resolve(payout);
}
});
});
};
getPayoutsPending(uid)
.then(getPayoutsAmount)
.then(buildPaymentRequest)
.then(createPayout)
.then(updatePaymentsPending(uid, sender_batch_id))
.then(() => {
response.status('200').end();
return;
})
.catch((err) => {
console.log(err);
response.status('500').end();
return console.error(error);
})
});
Alternatively, throwing an // eslint-disable at the top of the file would fix your issue ;)
here is my code in which I try to update some documents of my database (MongoDB)
using a for loop. however I want to run the next code after the loop is completed,
as for example, I want to use some variables calculated inside the loop after it finished.
How can I do such using callback, promise, etc?
numPktUpdated = 0;
for (key in InvoicesPUT) {
Invoice.findOneAndUpdate({ InvoiceNumber: InvoicesPUT[key].InvoiceNumber }, InvoicesPUT[key]).exec()
.then((doc) => {
console.log("Update Succeeded")
numPktUpdated = numPktUpdated + 1;
})
.catch((err) => {
return resp.send(JSON.stringify({
"status": "error",
"message": "DB Error while Updating: Wrong Packet"
}));
console.log(err);
})
}
resp.send(numPktUpdated);
Here numPktUpdated = 0 is sent to client, although its real value after the loop is something else.
Thanks.
You should be able to do this with Promise.all():
Promise.all(InvoicesPUT.map(InvoicePUT => {
return Invoice.findOneAndUpdate({
InvoiceNumber: InvoicePUT.InvoiceNumber
}, InvoicePUT).exec()
.then(doc => {
console.log("Update Succeeded");
numPktUpdated += 1;
})
}))
.catch(err => {
return resp.send(JSON.stringify({
"status": "error",
"message": "DB Error while Updating: Wrong Packet"
}));
console.log(err);
})
.then(() => {
// What you want to do after the loop ...
})
Try to put code to the function, then run it with async
async function forLoopFunction(args){//here is a for loop
}
let ret = await forLoopFunction(args);
If you are using an older version of Node v7.6, which brought support for the async / await pattern, you could use a simple Promise:
function updateDocs() {
return new Promise(function(resolve, reject) {
numPktUpdated = 0;
for (key in InvoicesPUT) {
Invoice.findOneAndUpdate({ InvoiceNumber: InvoicesPUT[key].InvoiceNumber }, InvoicesPUT[key]).exec()
.then((doc) => {
console.log("Update Succeeded")
numPktUpdated = numPktUpdated + 1;
resolve(numPktUpdated);
})
.catch((err) => {
console.log(err);
reject(err);
return resp.send(JSON.stringify({
"status": "error",
"message": "DB Error while Updating: Wrong Packet"
}));
})
}
});
}
updateDocs().then(function(numPktUpdated) {
resp.send(numPktUpdated);
})
.catch(function(err) {
// handle error
});
Thanks, It worked with just a minor modification:
Promise.all(InvoicesPUT.map(invoice => {
return Invoice.findOneAndUpdate({InvoiceNumber: invoice.InvoiceNumber}, invoice).exec()
.then((doc) => {
console.log("Update Succeeded")
numPktUpdated = numPktUpdated + 1;
})
}))
.catch((err) => {
return resp.send(JSON.stringify({
"status": "error",
"message": "DB Error while Updating: Wrong Packet"
}));
console.log(err);
})
.then((resolve) => {
return resp.send(JSON.stringify({
"status": "succed",
"LastSavedGUID": 654323,
"SyncDate": 1,
"pagenumber": numPktUpdated
}));
})