I am trying to build a financial system in NodeJS. I am refactoring my code from using callbacks to using Promises to minimize nesting. But I am struggling with understanding what would the proper meaning of a Resolve or a Rejection should be. Let's say in a situation where I am checking the balance before debiting an account, would the result where the balance is insufficient be a resolve or a reject? Does Resolve/Reject pertain to success or failure of code execution or that of the logical process?
There is lots of design flexibility for what you decide is a resolved promise and what is a rejected promise so there is no precise answer to that for all situations. It often takes some code design judgement to decide what best fits your situataion. Here are some thoughts:
If there's a true error and any sequence of operations of which this is one should be stopped, then it's clear you want a rejection. For example, if you're making a database query and can't even connect to the database, you would clearly reject.
If there's no actual error, but a query doesn't find a match, then that would typically not be a rejection, but rather a null or empty sort of resolve. The query found an empty set of results.
Now, to your question about debiting an account. I would think this would generally be an error. For concurrency reasons, you shouldn't have separate functions that check the balance and then separately debit it. You need an atomic database operation that attempts to debit and will succeed or fail based on the current balance. So, if the operation is to debit the account by X amount and that does not succeed, I would think you would probably reject. The reason I'd select that design option is that if debiting the account is one of several operations that are part of some larger operation, then you want a chain of operations to be stopped when the debit fails. If you don't reject, then everyone who calls this asynchronous debit function will have to have separate "did it really succeed" test code which doesn't seem proper here. It should reject with a specific error that allows the caller to see exactly why it rejected.
Let's say in a situation where I am checking the balance before debiting an account, would the result where the balance is insufficient be a resolve or a reject?
There's room to go either way, but as I said above, I'd probably make it a reject because if this was part of a sequence of operations (debit account, then place order, send copy of order to user), I'd want the code flow to take a different path if this failed and that's easiest to code with a rejection.
In general, you want the successful path through your code to be able to be coded like this (if possible):
try {
await op1();
await op2();
await opt3();
} catch(e) {
// handle errors here
}
And, not something like:
try {
let val = await op1();
if (val === "success") {
await op2();
....
} else {
// some other error here
}
} catch(e) {
// handle some errors here
}
Does Resolve/Reject pertain to success or failure of code execution or that of the logical process?
There is no black and white here. Ultimately, it's success or failure of the intended operation of the function and interpreting that is up to you. For example, fetch() in the browser resolves if any response is received from the server, even if it is a 404 or 500. The designers of fetch() decided that it would only reject if the underlying networking operation failed to reach the server and they would leave the interpretation of individual status codes to the calling application. But, often times, our applications want 4xx and 5xx statuses to be rejections (an unsuccessful operation). So, I sometimes use a wrapper around fetch that converts those statuses to rejections. So, hopefully you can see, it's really up to the designer of the code to decide which is most useful.
If you had a function called debitAccount(), it would make sense to me that it would be considered to have failed if there are insufficient funds in the account and thus that function would reject. But, if you had a function called checkBalance() that checks to see if a particular balance is present or not, then that would resolve true or false and only actual database errors would be rejections.
To use resolve or reject depends on your implementation.
Basic Promise upgrade from callback:
return new Promise(function (resolve, reject) {
myFunction(param, function callback(err, data) {
if (err)
reject(err);
else
resolve(data);
})
})
and shortcut to this is util.promisify(myFunction)
const util = require('util');
function myFunction(param, callback) { ... }
myFunctionAsync = util.promisify(myFunction)
myFunctionAsync(param).then(actionSuccess).catch(actionFailure)
As you see, reject is used to pass the error, and resolve to pass the result.
You can use with resolve/reject:
function finacialOperationAsync() {
return new Promise((resolve, reject) => {
const balance = checkBalance();
if (balance < 0)
reject(new Error('Not enough balance'))
else
finacialOperation((err, res) => {
if (err)
reject(err);
then
resolve(res);
});
})
}
finacialOperationAsync().then(actionOnSuccess).catch(actionOnFailure)
Or you can use true/false and async/await:
function checkBalanceAsync() {
return new Promise(resolve => {
resolve(checkBalance() >= 0);
})
}
async function finacialOperationAsync() {
if (await checkBalanceAsync()) {
const res = await finacialOperationAsync();
return actionOnSuccess(res);
} else
return actionFailure();
}
All err variables in the callback style code should be mapped to rejection in promises. In this way, promisify decorator converts callbacks to promises, so you should take this approach to maintain parity.
Related
While working with mongoose, I have the following two async operations
const user = await userModel.findOneAndDelete(filter, options);
await anotherAsyncFunctionToDeleteSomething(user); // not a database operation
But I need to ensure that both deletions happen or reject together. I've thought about putting trycatch on second function and recreate the user document if any error is caught (in the catch block). However, this doesn't seem like an efficient (or at least appealing) solution.
If second operation is totally unrelated then there's nothing much you can do. You can later ensure if both records were if that's extremely necessary. You could use Promise.all() to run both promises simultaneously but it'll throw error if either fails so you can retry it.
But as you mentioned, you need result of first operation before running the second then you'll have to run those individually.
Yes, you can catch any errors thrown in the second promise and recreate a document with same ID as shown below:
const user = await userModel.findOneAndDelete(filter, options);
try {
await anotherAsyncFunctionToDeleteSomething(user);
} catch (e) {
console.log(e)
await collection.insertOne({ _id: new ObjectId("62....54"), ...otherData });
}
I am getting a mongoose error when I attempt to update a user field multiple times.
What I want to achieve is to update that user based on some conditions after making an API call to an external resource.
From what I observe, I am hitting both conditions at the same time in the processUser() function
hence, user.save() is getting called almost concurrently and mongoose is not happy about that throwing me this error:
MongooseError [ParallelSaveError]: Can't save() the same doc multiple times in parallel. Document: 5ea1c634c5d4455d76fa4996
I know am guilty and my code is the culprit here because I am a novice. But is there any way I can achieve my desired result without hitting this error? Thanks.
function getLikes(){
var users = [user1, user2, ...userN]
users.forEach((user) => {
processUser(user)
})
}
async function processUser(user){
var result = await makeAPICall(user.url)
// I want to update the user based on the returned value from this call
// I am updating the user using `mongoose save()`
if (result === someCondition) {
user.meta.likes += 1
user.markModified("meta.likes")
try {
await user.save()
return
} catch (error) {
console.log(error)
return
}
} else {
user.meta.likes -= 1
user.markModified("meta.likes")
try {
await user.save()
return
} catch (error) {
console.log(error)
return
}
}
}
setInterval(getLikes, 2000)
There are some issues that need to be addressed in your code.
1) processUser is an asynchronous function. Array.prototype.forEach doesn't respect asynchronous functions as documented here on MDN.
2) setInterval doesn't respect the return value of your function as documented here on MDN, therefore passing a function that returns a promise (async/await) will not behave as intended.
3) setInterval shouldn't be used with functions that could potentially take longer to run than your interval as documented here on MDN in the Usage Section near the bottom of the page.
Hitting an external api for every user every 2 seconds and reacting to the result is going to be problematic under the best of circumstances. I would start by asking myself if this is absolutely the only way to achieve my overall goal.
If it is the only way, you'll probably want to implement your solution using the recursive setTimeout() mentioned at the link in #2 above, or perhaps using an async version of setInterval() there's one on npm here
Let's say I have a function:
const someAction = async(): Promise<string> => {
/* do stuff */
};
And I have some code that just needs to run this action, ignoring the result. But I have a bug - I don't want for action to complete:
someAction();
Which, instead, should've been looked like this:
await someAction();
Now, I can check that this action was ran:
const actionStub = sinon.stub(someAction);
expect(actionStub).to.have.been.calledWith();
But what's the most concise way to check that this promise have been waited on?
I understand how to implement this myself, but I suspect it must have already been implemented in sinon or sinon-chai, I just can't find anything.
I can certainly say that nothing like this exists in sinon or sinon-chai.
This is a difficulty inherent to testing any promise-based function where the result isn't used. If the result is used, you know the promise has to be resolved before proceeding with said result. If it is not, things get more complex and kind of outside of the scope of what sinon can do for you with a simple stub.
A naive approach is to stub the action with a fake that sets some variable (local to your test) to track the status. Like so:
let actionComplete = false;
const actionStub = sinon.stub(someAction).callsFake(() => {
return new Promise((resolve) => {
setImmediate(() => {
actionComplete = true;
resolve();
});
});
});
expect(actionStub).to.have.been.calledWith();
expect(actionComplete).to.be.true;
Of course, the problem here is that awaiting any promise, not necessarily this particular one, will pass this test, since the variable will get set on the next step of the event loop, regardless of what caused you to wait for that next step.
For example, you could make this pass with something like this in your code under test:
someAction();
await new Promise((resolve) => {
setImmediate(() => resolve());
});
A more robust approach will be to create two separate tests. One where the promise resolves, and one where the promise rejects. You can test to make sure the rejection causes the containing function to reject with the same error, which would not be possible if that specific promise was not awaited.
const actionStub = sinon.stub(someAction).resolves();
// In one test
expect(actionStub).to.have.been.calledWith();
// In another test
const actionError = new Error('omg bad error');
actionStub.rejects(actionError);
// Assuming your test framework supports returning promises from tests.
return functionUnderTest()
.then(() => {
throw new Error('Promise should have rejected');
}, (err) => {
expect(err).to.equal(actionError);
});
Some assertion libraries and extensions (maybe chai-as-promised) may have a way of cleaning up that use of de-sugared promises there. I didn't want to assume too much about the tools you're using and just tried to make sure the idea gets across.
I created a separate file with all my queries using pg-promise module for Node. While for most of them I just use req, res after a query, for one I want to return a value back. It does not work. It returns undefined.
passportLogin: (email)=> {
db.one(`SELECT userid
FROM user`)
.then( result => {
return result;
})
.catch( error => {
return error;
});
}
That code has two problems at the same time:
invalid promise use, when inside .catch you do return result, that's not how promise rejects are handled, you must either provide the error handling, or re-throw / re-reject the error.
invalid use of pg-promise library. You use method one that's supposed to reject when anything other than 1 record is returned, as per the method's documentation, and at the same time you are saying I need to catch if it returns more than one row... , which is a logical contradiction.
The result of it is as follows: your query executes successfully, and returns more than one record, which in turn makes method one reject, and then you take the rejection reason and turn it into a resolved one by doing return result. In all, your code is broken all over the place.
First, with pg-promise you are supposed to use the right method, according to the number of records you are expecting back, see The Basics.
And then handle .then/.catch according to your business logic. I can't be more specific here, because you did not provide further details on this.
I'm new to node.js and using it for a backend that takes data from syslog messages and stores it to a database.
I've run into the following type of serial operations:
1. Query the database
2. If the query value is X do A. otherwise do B.
A. 1. Store "this" in DB
2. Query the database again
3. If the query value is Y do P. otherwise do Q.
P. Store "something"
Q. Store "something else"
B. 1. Store "that" in the DB
2. Store "the other thing" in the DB
The gist here is that I have some operations that need to happen in order, but there is branching logic in the order.
I end up in callback hell (I didn't know what that is when I came to Node.. I do now).
I have used the async library for things that are more straight forward - like doing things in order with async.forEachOfSeries or async.queue. But I don't think there's a way to use that if things have to happen in order but there is branching.
Is there a way to handle this that doesn't lead to callback hell?
Any sort of complicated logic like this is really, really going to benefit from using promises for every async operation, especially when you get to handling errors, but also just for structuring the logic flow.
Since you haven't provided any actual code, I will make up an example. Suppose you have two core async operations that both return a promise: query(...) and store(...).
Then, you could implement your above logic like this:
query(...).then(function(value) {
if (value === X) {
return store(value).then(function() {
return query(...).then(function(newValue) {
if (newValue === Y) {
return store("something");
} else {
return store("something else");
}
})
});
} else {
return store("that").then(function() {
return store("the other thing");
});
}
}).then(function() {
// everything succeeded here
}, function(err) {
// error occurred in anyone of the async operations
});
I won't pretend this is simple. Implemented logic flow among seven different async operation is just going to be a bit of code no matter how you do it. But, promises can make it less painful than otherwise and massively easier to make robust error handling work and to interface this with other async operations.
The main keys of promises used here are:
Make all your async operations returning promises that resolve with the value or reject with the failure as the reason.
Then, you can attach a .then() handler to any promise to see when the async operation is finished successfully or with error.
The first callback to .then() is the success handler, the second callback is the error handler.
If you return a promise from within a .then() handler, then that promise gets "chained" to the previous one so all success and error state becomes linked and gets returned back to the original promise state. That's why you see the above code always returning the nested promises. This automates the returning of errors all the way back to the caller and allows you to return a value all the way back to the caller, even from deeply nested promises.
If any promise in the above code rejects, then it will stop that chain of promises up until an actual reject handler is found and propagate that error back to that reject handler. Since the only reject handler in the above code is at the to level, then all errors can be caught and seen there, no matter how deep into the nested async mess it occurred (try doing that reliably with plain callbacks - it's quite difficult).
Native Promises
Node.js has promises built in now so you can use native promises for this. But, the promise specification that node.js implements is not particularly feature rich so many folks use a promise library (I use Bluebird for all my node.js development) to get additional features. One of the more prominent features is the ability to "promisify" an existing non-promise API. This allows you to take a function that works only with a callback and create a new function that works via a promise. I find this particularly useful since many APIs that have been around awhile do not natively return promises.
Promisifying a Non-Promise Interface
Suppose you had an async operation that used a traditional callback and you wanted to "promisify it". Here's an example of how you could do that manually. Suppose you had db.query(whatToSearchFor, callback). You can manually promisify it like this:
function queryAsync(whatToSearchFor) {
return new Promise(function(resolve, reject) {
db.query(whatToSearchFor, function(err, data) {
if (!err) {
reject(err);
} else {
resolve(data);
}
});
});
}
Then, you can just call queryAsync(whatToSearchFor) and use the returned promise.
queryAsync("foo").then(function(data) {
// data here
}, function(err) {
// error here
});
Or, if you use something like the Bluebird promise library, it has a single function for promisifying any async function that communicates its result via a node.js-style callback passed as the last argument:
var queryAsync = Promise.promisify(db.query, db);