The code is a part of a much bigger and complicated code, so I am just gonna put the relevant snippets to my question.
I have this promise.all snippet:
Promise.all(receivedObjs.arrayCMsIds.map(cmid =>
server.writeAttachedCMinfo(invId,cmid)))
.then(function (results) { // for promise all
return res.json(apiHelp.success(results,"success"));
}).catch(function (error) {
res.json(apiHelp.error(error, error));
});
And this long complicated writeAttachedCMinfo function:
server.writeAttachedCMinfo = function (invId,cmid) {
return new Promise(function (resolve, reject) {
console.log("writeAttachedCMinfo");
console.log("invoiceId " + invId);
console.log("cmid "+ cmid);
var invoiceId = JSON.stringify(invId);
var cmId = JSON.stringify(cmid);
var invIdString = invoiceId;
var cmIdString = cmId;
invIdString = invIdString.slice(1, -1);
cmIdString = cmIdString.slice(1, -1);
var projection = 'gwCode certifiedInvoiceAmount buyerReference supplierReference invoiceNo invoiceSerialNo invoiceFiles creditMemos';
ubiqInvoice.findById(invIdString, projection).then(function (dbInvoice) {
var intInvCertifiedAmount = parseInt(dbInvoice.certifiedInvoiceAmount);
creditMemo.findById(cmIdString).then(function (dbCreditMemo) {
var intCreditMemoAmount = parseInt(dbCreditMemo.creditMemoAmount);
if (intInvCertifiedAmount <= intCreditMemoAmount) {
console.log('cm bigger than invoice')
return new Error ('CMisbiggerThanInvoice');
}
if (dbCreditMemo.isAssociated) {
return new Error ('CMisAssociated');
}
if (dbInvoice.gwCode === "100000000000"
|| dbInvoice.gwCode === "110000000000"
|| dbInvoice.gwCode === "111200000000"
|| dbInvoice.gwCode === "111100000000"
|| dbInvoice.gwCode === "111110000000"
) {
var creditMemoEntry = {
id: guid.create().value,
batchId: dbCreditMemo.batchId,
invoiceId: dbInvoice._id,
recordTypeCode: "CM",
buyerReference: dbInvoice.buyerReference,
supplierReference: dbInvoice.supplierReference,
creditMemoNo: dbCreditMemo.creditMemoNo,
creditMemoIssuingDate: dbCreditMemo.creditMemoIssuingDate,
creditMemoEffectiveDate: dbCreditMemo.creditMemoEffectiveDate,
lastModificationDate: dbCreditMemo.lastModificationDate,
currencyCode: dbCreditMemo.currencyCode,
creditMemoAmount: dbCreditMemo.creditMemoAmount,
hashCode: dbCreditMemo.hashCode,
description: dbCreditMemo.description,
uploadDate: dbCreditMemo.uploadDate,
isAssociated: true,
}
dbInvoice.creditMemos.push(creditMemoEntry);
dbInvoice.certifiedInvoiceAmount = dbInvoice.certifiedInvoiceAmount - dbCreditMemo.creditMemoAmount;
dbInvoice.save();
dbCreditMemo.isAssociated = true;
dbCreditMemo.save();
resolve(dbInvoice)
}
else { return new Error ('wrongggwcode'); }
})
});
}), function (error) {
console.log("error: " + error);
}
}
My goal, is to force error throwing in case one of the if conditions aren't met, and I want to pass the error to client in a form of a custom message so i can use it on the client said for displaying various errors , such as 'CMisbiggerThanInvoice'
if (intInvCertifiedAmount <= intCreditMemoAmount) {
console.log('cm bigger than invoice')
return new Error ('CMisbiggerThanInvoice');
}
I am just trying to figure out a way to pass the error from the writeAttachedCMinfo function to the promise.all's .catch(function (error) but it's not working, the promise.all is always returning success even if one of the if conditions aren't met.
I have tried reject('CMisbiggerThanInvoice'), reject(new Error('CMisbiggerThanInvoice')...all the same.
how can i really force the promise function to return an error?
In the context of a promise you should actually throw the error:
throw new Error('wrongggwcode');
If this executes in a promise constructor callback or then callback, it can be caught via the catch method (or second argument of then) and the argument of the callback you pass to it will be the error (object).
Calling reject from within a then callback will obviously not work, since you don't have access to reject there, but it will work in a promise constructor callback.
Simple example:
new Promise((resolve, reject) => {
setTimeout( () => reject(new Error('this is an error')) );
}).then( (value) => console.log('resolved with ' + value) )
.catch( (error) => console.log('error message: ', error.message) );
Nesting
When you have nested promises within then callbacks, make sure you always return the value returned by the inner promise as the return value of the outer then callback.
So in your case do this:
return creditMemo.findById( ....
//^^^^
For the same reason you need to do:
return ubiqInvoice.findById( ....
//^^^^
It would lead to far for this question/answer, but it is best practice to avoid nesting promise then calls all together. Instead of calling then on a nested promise, just return the promise without the then call, and apply that then call one level higher, so that you have a "flat" chain of then calls. This is just a matter of best practice, although it should also work like you have done it provided you always return the inner promises.
Position of the error handler
The error handler is placed at a wrong position; in fact you are using the comma operator. In short, you have this in your code:
new Promise(function (resolve, reject) {
// ... //
}), function (error) {
console.log("error: " + error);
}
The function after the comma is never executed as it is not an argument to a method call. It just sits behind a comma operator.
What you want is a catch method call on the new promise, and cascade the error in there so that Promise.all will also receive the rejection:
return new Promise(function (resolve, reject) {
// ... //
}).catch(function (error) {
console.log("error: " + error);
throw error; // cascade it
});
In server.writeAttachedCMinfo() the main things are :
to get rid of the new Promise() wrapper - it's not necessary - (Promise constructor anti-pattern).
to throw errors, rather than return them.
Also, because there's only one invId, there's effectively only one dbInvoice and it needn't be retrieved over and over from the database on each call of server.writeAttachedCMinfo(). In fact it shouldn't be retrieved over and over, otherwise each dbInvoice.save() may well overwrite earlier saves within the same overall transaction. It will be safer to accumulate all creditMemos and progressively decrement the certifiedInvoiceAmount in a single dbInvoice object, and evetually perform a single `dbInvoice.save().
server.writeAttachedCMinfo = function(dbInvoice, cmid) {
return creditMemo.findById(JSON.stringify(cmid).slice(1, -1))
.then(dbCreditMemo => {
if(parseInt(dbInvoice.certifiedInvoiceAmount) <= parseInt(dbCreditMemo.creditMemoAmount)) {
throw new Error('CMisbiggerThanInvoice');
// ^^^^^
}
/* all sorts of synchronous stuff */
/* all sorts of synchronous stuff */
/* all sorts of synchronous stuff */
return dbCreditMemo; // deliver dbCreditMemo via returned Promise
});
}
Now, in the caller :
ubiqInvoice.findById() can be called once.
perform checks on dbInvoice, and throw on failure.
pass dbInvoice, rather than invId to server.writeAttachedCMinfo() at each call.
all saving can be done here, not in server.writeAttachedCMinfo().
As a consequence, the caller subsumes some of the code that was in server.writeAttachedCMinfo() :
ubiqInvoice.findById(JSON.stringify(invId).slice(1, -1), 'gwCode certifiedInvoiceAmount buyerReference supplierReference invoiceNo invoiceSerialNo invoiceFiles creditMemos')
.then(dbInvoice => {
if(dbCreditMemo.isAssociated) {
throw new Error('CMisAssociated');
// ^^^^^
}
if(dbInvoice.gwCode === '100000000000'
|| dbInvoice.gwCode === '110000000000'
|| dbInvoice.gwCode === '111200000000'
|| dbInvoice.gwCode === '111100000000'
|| dbInvoice.gwCode === '111110000000'
) {
return Promise.all(receivedObjs.arrayCMsIds.map(cmid => {
return server.writeAttachedCMinfo(dbInvoice, cmid)
.catch(error => {
console.log(error);
return null;
});
}))
.then(dbCreditMemos => {
return Promise.all(dbCreditMemos.map(memo => {
return memo ? memo.save() : null;
}))
.then(() => dbInvoice.save())
.then(() => {
res.json(apiHelp.success(dbInvoice, 'success'));
});
});
} else {
throw new Error('wrongggwcode');
// ^^^^^
}
})
.catch(error => {
console.log('error: ' + error);
res.json(apiHelp.error(error, error));
});
The whole area around catching/handling errors arising from server.writeAttachedCMinfo() requires more thought. On the one hand, you might want successful sub-transactions to be saved and errors to be swallowed (as coded above) while on the other hand, you might want any single failure to cause nothing to be saved.
Another consideration is whether server.writeAttachedCMinfo() calls should be made sequentially, which would control the priority by which the sub-transactions access the credit balance. As it stands, with parallel requests, it's a bit of a free-for-all.
That addresses a bit more than the question asked for but hopefully it will be useful.
Related
I found the following code in a project, that I do not understand.:
get(key, store = null) {
if (!key) {
return new Error('There is no key to get!');
}
let dbstore = this.localforage;
if (store !== null) {
dbstore = store;
}
return dbstore
.getItem(key)
.then(function(value) {
return value;
})
.catch(function(err) {
return new Error('The key (' + key + ") isn't accessible: " + err);
});
}
Why return new Error('There is no key to get!'); instead of throw new Error('There is no key to get!');?
Also why not throw an error in the catch block?
When you design a function interface and there are errors to deal with, you have a design choice for how to return errors. If the function is synchronous, you can either return some sentinel value that indicates an error and is easily distinguished from an actual result (often null in Javascript) or you can throw an exception or you can return an object that has a property that indicates the success or failure of the operation.
When you have an asynchronous operation with a promise interface, one would usually reject the Promise with an Error object as the reject reason to signify an error. That's the core design theory of promises. Success resolves with an optional value, errors reject with a reason.
This block of code:
return dbstore
.getItem(key)
.then(function(value) {
return value;
})
.catch(function(err) {
return new Error('The key (' + key + ") isn't accessible: " + err);
});
is resolving the returned promise with either a value or an Error object. This is generally not how promise code is written because it will require the caller to test the type of the resolved value to figure out if there's an error or not which is not the simple, straightforward way to use promises. So, to your question, you would usually do this:
return dbstore.getItem(key).catch(function(err) {
throw new Error('The key (' + key + ") isn't accessible: " + err);
});
There are other signs in this function, that it's just bad code.
.then(function(value) {return value;}) is completely superfluous and unnecessary. It adds no value at all. The value is already the resolved value of the promise. No need to declare it again.
The function sometimes returns a promise and sometimes throws a synchronous exception.
This is even a further pain to use. If you look at the first if (!key) { statement, it returns an Error object if the key argument isn't supplied. That means that to use this function you have to catch synchronous exceptions, provide .then() and .catch() handlers AND check the type of the resolved promise to see if it happens to be an error object. This function is a nightmare to use. It's bad code.
To use the function as it is, the caller would likely have to do this:
let retVal = someObj.get(aKey);
if (typeof retVal === Error) {
// got some synchronous error
} else {
retVal.then(val => {
if (typeof val === Error) {
// got some asynchronous error
} else {
// got an actual successful value here
}
}).catch(err => {
// got some asynchronous error
})
}
The function implementation probably should be this:
get(key, store = null) {
if (!key) {
return Promise.reject(new Error('There is no key to get!'));
}
let dbstore = store || this.localforage;
return dbstore.getItem(key).catch(function(err) {
throw new Error('The key (' + key + ") isn't accessible: " + err);
});
}
This can then be used like this:
someObj.get(aKey).then(val => {
// got some successful value here
}).catch(err => {
// got some error here
});
Compare the simplicity for the caller here to the mess above.
This implementation has these consistencies:
It always returns a promise. If key isn't supplied, it returns a rejected promise.
All errors come via a rejected promise.
The value the promise resolves with is always an actual successful value.
There's no .then() handler that does nothing useful.
I'm faced with a small issue when trying to chain complex function calls with Promises and callbacks.
I have a main function, which calls subroutines. In these routines API calls are made.
For Example:
function handle(){
new Promise(function(resolve, reject){
let result = doAPICall1()
if (result === true) resolve(true);
reject(JSON.stringify(result))
}).then(function(){
let result = doAPICall2()
if (result === true) return true
throw new Error(JSON.stringify(result))
}).catch(error){
console.error(JSON.stringify(error))
}
}
function doAPICall1(){
axios.get('...').then(function(){
return true
}).catch(function(error){
return error
})
}
function doAPICall2(){
axios.get('...').then(function(){
return true
}).catch(function(error){
return error
})
}
But when I execute this example, doAPICall2 will be executed, while doAPICall1 is still running.
It only occures when long running calls are made.
Does anyone can give me a hint? Thank you!
You're overdoing manually a lot of things that Promises already do for you:
axios.get already returns a Promise, so there is no point in return a the response when it resolves and return a false when it rejects. A catch handler at the end of a Promise chain already handles all errors that may arise during the chain, so you don't need to catch every Promise.
I would do something like:
function doAPICall1(){
return axios.get('...');
}
function doAPICall2(){
return axios.get('...');
}
function handle(){
// in case you would use the api calls results.
let firstResult = null;
let secondResult = null;
return doAPICall1()
.then(res => {firstResult = res})
.then(() => doAPICall2())
.then(res => {
secondResult = res;
return []
})
}
I guess you will use the Api calls results for something. With the code above, you could consume the handle() function like follows:
function someSortOfController(){
handle().then(results => {
console.log(results[0]); // first api call result
console.log(results[1]); // second api call result
})
.catch(err => {
// here you will find any error, either it fires from the first api call or from the second.
// there is *almomst* no point on catch before
console.log(err);
})
}
There, you will access any error, either it came from the first api call or the second. (And, due to how Promises work, if the first call fails, the second won't fire).
For more fine grained error control, you may want to catch after every Promise so you can add some extra logs, like:
function doAPICall1(){
return axios.get('...')
.catch(err => {
console.log('the error came from the first call');
throw err;
});
}
function doAPICall2(){
return axios.get('...')
.catch(err => {
console.log('the error came from the second call');
throw err;
});
}
Now, if the first api call fails, everything will work as before (since you're throwing the error again in the catch), but you have more control over error handling (maybe the error returning from API calls is not clear at all and you want this kind of control mechanism).
Disclaimer
This answer doesn't answer why your code acts like it does. However, there are so much things wrong in your code, so I think providing you with an example about using Promises is more valuable.
Don't worry and take some time to understand Promises better. In the example code below, doAPICall function return a Promise which resolves to a value, not the value itself.
function handle() {
doAPICall().then(result => {
//do something with the result
}).catch(error => {
//catch failed API call
console.error(error)
})
}
doAPICall() {
// this returns a Promise
return axios.get(...)
}
I'm having an issue with a Node.js function. I'm fairly certain that it is just an issue with the function being asynchronous but I want to be sure. I am checking to see if a certain path that was entered by the user is valid by checking if it exists.
var directoryExists = exports.directoryExists = function(filePath) {
return new Promise(function(resolve, reject) {
if (fs.statSync(filePath).isDirectory()){
resolve("Valid");
} else {
reject("Invalid");
}
});
}
These are my calls to the function:
files.directoryExists(sourcePath).then((msg) => {
console.log(msg);
}).catch(function(){
console.error("Promise Rejected");
});
files.directoryExists(destPath).then((msg) => {
console.log(msg);
}).catch(function(){
console.error("Promise Rejected");
});
I'm very new to the whole concept of asynchronous programming and promises so this is becoming quite frustrating. Any help would be appreciated.
It's not really the asynchronous thing that's catching you out, although there's a change you can make to improve that.
statSync can throw an exception (if the path doesn't match anything, for instance); you're not handling that, and so when it throws, that gets converted into a rejection. If you looked at the argument you're getting in your catch handler, you'd see the exception that it raises.
The async improvement is that since you're using a Promise, there's no reason to use statSync. Just use stat so you don't tie up the JavaScript thread unnecessarily.
So something along the lines of:
var directoryExists = exports.directoryExists = function(filePath) {
return new Promise(function(resolve, reject) {
// Make the request asynchronous
fs.stat(filePath, function(err, data) {
// If there was an error or it wasn't a directory...
if (err || !data.isDirectory()) {
// ...reject
reject(err || new Error("Not a directory");
} else {
// All good
resolve(data);
}
});
});
};
Of course, you may choose to have it resolve with false if the thing isn't a directory or any of several other choices; this just gets you further along.
For instance, having an error still be a rejection, but resolving with true/false for whether something that exists is a directory; this provides the maximum amount of information to the caller but makes them work a bit harder if all they care about is true/false:
var directoryExists = exports.directoryExists = function(filePath) {
return new Promise(function(resolve, reject) {
// Make the request asynchronous
fs.stat(filePath, function(err, data) {
if (err) {
// Reject on error
reject(err);
} else {
// Return result on success
resolve(data.isDirectory());
}
});
});
};
or making it always resolve, with false if there's no match or there is a match but it's not a directory:
var directoryExists = exports.directoryExists = function(filePath) {
return new Promise(function(resolve) {
// Make the request asynchronous
fs.stat(filePath, function(err, data) {
resolve(!err && data.isDirectory());
});
});
};
Lots of ways for this function to behave, it's up to you.
I have the following code. It works fine when f2 throws no error.
If there is an error, it generates an Unhandled rejection Error.
What's the proper way to rewrite the code to avoid Unhandled rejection Error and propagate it correctly to catch in f1?
let Bluebird = require('bluebird'),
mkdirp = Bluebird.promisify(require('mkdirp')),
request = Bluebird.promisify(require('request')),
writeFile = Bluebird.promisify(require('fs').writeFile);
function f1() {
.........
f2(path, fileName, options).then(.....).catch(....);
}
function f2(path, fileName, options) {
p = mkdirp(path).then(request(options).then(res => {
if (res[0].statusCode === 200) {
writeFile(fileName, res[0].body);
return res[0].body;
} else {
throw new Error(res[0].statusCode + ': ' + res[0].body);
}
}));
return p;
}
The problem is that you are passing a promise into .then() in f2. .then() will ignore anything that is not a function, so all that f2 is really returning is a promise for mkdirp(this.path) and that's a big bug for a few reasons. If an error is thrown in request(options)'s then handler, then there will be nothing to handle it.
Also, you are not doing anything to handle a possible error from writeFile. If you call writeFile, you either need to return a promise chain that includes it, or add logic to handle it within f2.
Since it looks like you can run mkdirp() and request() in parallel here, but you are not using the result of mkdirp() I would say this is the way to go:
function f2(path, fileName, options) {
var p = mkdirp(path).return(request(options)).then(res => {
if (res[0].statusCode === 200) {
return writeFile(fileName, res[0].body)
.return(res[0].body);
} else {
throw new Error(res[0].statusCode + ': ' + res[0].body);
}
});
return p;
}
Background: I have a PHP background and this is my first application using MEAN stack.
I need to save a record but before I must to check if there is any record under the same id already saved in the DB.
In PHP I would do something like this:
Once the user clicks "Save":
1) Call the function to check if an entry with that id already exists
2) If it doesnt, call the save function.
In Javascript, I'm getting a little confused with Promises and so on.
Can somebody give me some light here?
Right now, I'm doing the following:
In the save api, I call this function to check if the record already exists in the DB:
recordExists = findTranscationByBill(billId);
function findTransactionByBill(billId){
results = new promise(function(resolve, reject){
Transactions.find({billId : billId},function(err, transactions){
if(err)
reject("Error: "+err);
//console.log(transactions);
resolve(transactions);
});
});
results.then(function(data){
console.log('Promise fullfilled: '+ data);
}, function(error){
console.log('Promise rejected: ' + error);
});
return $results;
}
The problem is that I think I'm not using promise properly, as my variable doesn't get populated (because its Async).
In the console.log I see that the promise is being fulfilled however, the variable returns as [object Object]
I'm stucked with this problem because I don't know if I should carry on thinking as PHP mindset or if there is a different approach used in Javascript.
Thanks in advance!
In my opinion you could just as well use a callback for this, and since MongoDB has a count method, why not use it
function findTransactionByBill(billId, callback){
Transactions.count({billId : billId}, function(err, count){
if (err) {
callback(err, false);
} else {
callback(null, count !== 0);
}
});
}
and to use it
findTransactionByBill(billId, function(err, exists) {
if (err) {
// handle errors
} else if ( ! exists ) {
// insert into DB
}
}
I think the right function is:
function findTransactionByBill(billId){
var results = new promise(function(resolve, reject){
Transactions.find({billId : billId},function(err, transactions){
if(err) {
reject(err);
} else {
if (transactions.length === 0) {
reject('No any transaction');
} else {
//console.log(transactions);
resolve(transactions);
}
});
});
results.then(function(data){
console.log('Promise fullfilled: '+ data);
}, function(error){
console.log('Promise rejected: ' + error);
});
return results;
}
And then use it like this:
recordExists = findTranscationByBill(billId);
recordExists.then(function() {
// resolved, there are some transactions
}, function() {
// rejected. Error or no any transactions found
// may be you need to check reject result to act differently then no transactions and then error
});
I assume you are using mongodb native drive.
I think mongodb doesn't have promise built-in supported. So you have to promisify it by a little help from promise library. Please refer this if you want to use bluebird.
After promisifying, the code should looks like that (using bluebird):
Promise = require('bluebird');
// Promisify...
var _db = null;
var client = MongoClient.connectAsync('mongodb://localhost:27017/test')
.then(function(db) {
_db = db
return db.collection("myCollection").findOneAsync({ id: 'billId' })
})
.then(function(item) {
if (item)
_db.save(item);
})
.catch (err) {
// error handling
}
The above code is not perfect, because it introduced a global var, so the better version may be
Promise = require('bluebird');
// Promisify...
var client = MongoClient.connectAsync('mongodb://localhost:27017/test')
.then(function(db) {
return Promise.prop({
item: db.collection("myCollection").findOneAsync({ id: 'billId' },
db: db
})
})
.then(function(result) {
var item = result.item;
var db = result.db
if (item)
db.save(item);
})
.catch (err) {
// error handling
}
You need to check bluebird to know how to use it. Also they are many other promise libraries like q, when, but all are similar stuff.