Flow of logic when using promises - javascript

contrived example
Say we need to construct a Foo and Bar for a User in some Node application. We are expected to implement the constructFooForUser function, which must invoke some callback function with either
an Error
a null error, the created Foo, and the created Bar
To do this, we must make use of some database functions which fetch/create our objects, returning a Promise.
var constructFooForUser = function(userId, data, callback) {
db.users.find(userId)
.then(function(user) {
if (!user) { return callback(new Error('user not found')); }
db.foos.create(user, data)
.then(function(foo) {
db.bars.create(user, foo, data)
.then(function(bar) {
callback(null, foo, bar);
})
.catch(function(err) {
callback(err);
});
})
.catch(function(err) {
callback(err);
});
})
.catch(function(err) {
callback(err);
});
};
Is this the correct way to structure this type of promise based code?
I've seen examples of Promise code which looks like
doSomething()
.then(doSomethingElse)
.then(doYetAnotherThing)
.then(doLastThing)
.catch(handleError);
but I don't think this works in this scenario, as I need user, foo, and bar at the same time, and scope isn't shared between the chained functions.
I ask because the code looks repetitive, so I am wondering if something is unusual.
EDIT: Forgot to include foo in the creation of bar

How about storing foo in some variable until we need it?
var constructFooForUser = function(userId, data, callback) {
var gFoo;
db.users.find(userId)
.then(function(user) {
if (!user) { return callback(new Error('user not found')); }
return db.foos.create(user, data)
})
.then(function(foo) {
gFoo = foo;
return db.bars.create(user, data)
})
.then(function(bar) {
callback(null, gFoo, bar);
})
.catch(function(err) {
callback(err);
});
};

I typically (and yes I consider this a hacky workaround) accumulate the results in an array as I go.
firstThing()
.then(first => Promise.all([first, secondThing]))
.then(([first, second]) => Promise.all([first, second, thirdThing()]))
.catch(e => handleErr(e));
This accumulation pattern works, and with ES 2015 destructuring its not too clunky.

foo and bar don't seem to depend on each other, so we could make this part paralell:
var constructFooForUser = function(userId, data, callback) {
db.users.find(userId).then(function(user) {
//because rejecting a promise means nothing else than throwing an Error
if(!user) throw new Error('user not found');
//resolves as soon as both requests have been resolved.
//rejects if _any_ of the requests is rejecting
return Promise.all([
db.foos.create(user, data),
db.foos.create(user, data)
]);
}).then(
([foo, bar]) => callback(null, foo, bar), //utilizing array destructuring
(err) => callback(err)
);
}
I'm using .then(a, b) instead of .then(a).catch(b), because the latter would also catch Errors that are thrown in your callback-function (and we don't want that); that's your responsibility to deal with them. It would hide bugs in your code, by error-messages simply not showing up in the console.
Even your attempt with the nested promises could be streamlined by returning the promises and concatenating at least the error-part
var constructFooForUser = function(userId, data, callback) {
db.users.find(userId)
.then(function(user) {
if (!user) throw new Error('user not found');
return db.foos.create(user, data)
.then( foo => db.bars.create(user, data).then( bar => callback(null, foo, bar) ) );
})
.catch(function(err) {
callback(err);
});
};

Related

Function returns the object before, the findById() fill it with the required data

So, the problem is with the javascript basics I think.
'use strict';
module.exports = function accountWithSingleOrganisation(Account) {
Account.accountWithSingleOrganisation = function(accessToken, cb) {
accessToken.user(function(err, user) {
if (err) return cb(err);
let accData = {};
user.role(async (err, role) => {
if (err) return cb(err);
for (let it in role.scopes) {
if (role.scopes[it].account == null) break;
console.log('1');
await Account.findById(role.scopes[it].account, (err, account) => {
if (err) return cb(err);
console.log('2');
// Assigning Data Here
accData = Object.assign(accData, {
name: account.name,
id: account.id,
});
});
};
console.log(accData);
return cb(null, accData);
});
});
};
};
In my program I don't know how to use async await properly, due to which the for loop is executed and function returns the accData even before the assignment of data. Can you tell me how to use async await, or any other way through which, my function first assigns object it's data and, then return the data.
We, can take a response at place where we used await, and then push the data. That way it is wrking.

How to get error results from a promise then at the same time

At the end of a promise, I want to call a callback that expects err and results parameters. like so:
function callback(err, results) {
console.log("error", err);
console.log("result", results);
}
function async() {
return Promise.reject({"undefined": true});
}
// what i want
// async().then((err, results) => callback(err, results));
// what works
async().then(results => (), err => ());
But promise doesn't work like that.
A word of caution (as learned from experience): Be careful when mixing promises and callbacks. It can make things tricky later on.
However, to get exactly what you're asking for you can resolve your async function with an array, and then use array destructuring to pass (sometimes called "spreading") the arguments to your callback: (object destructuring as shown in the other answer is also a reasonable option)
function callback(err, results) {
console.log("error: ", err);
console.log("result: ", results);
}
function async() {
return Promise.resolve(["your errors", "your results" ]);
}
async().then(([error, results]) => callback(error, results));
This does what you want, but it's important to note what kinds of issues can come with this code. The async() function will need to "break the rules" with promise chaining, by resolving the promise with an error. (Either "resolve the error" and/or "reject the good results"). Plus, the order of that array matters. Even if it's an object, simple things like proper spelling of object properties (i.e., "err" prop vs. "error") can cause issues.
Also, in the callback function, it will likely be necessary to have some conditional that checks the error first and handles it before doing anything with the results.
If, however, only promises are used, then the code can be cleaned up a bit like this:
function async(){
var err = 'sad face'; // Play with these vars to see what happens
var results = 123;
if( err ){
return Promise.reject(err);
} else {
return Promise.resolve(results);
}
}
async()
.then((resp) => { console.log(resp) /* Work with resp, no conditionals needed :) Merrily proceed with your remaining code */ })
.catch((err) => { console.error(err)/* Err occurred, handle it here */ })
You could try rejecting with an object with two properties error and results. Then use destructuring and call your callback, like so:
function callback(err, results) {
console.log("error: ", err);
console.log("result: ", results);
}
function async() {
return Promise.reject({error: "your errors", results: "your results" });
}
async().catch(({error, results}) => callback(error, results));
But remember that resolve is for your success results and reject for your errors.

call the same http request if it fails but with different parameter to get default data

does it make sense to call the same http request call within the catch if the first one fails but with different parameter in order to return some default data ?
var defaultData = false;
clientService.getClients(defaultData)
.then(function (res) {
//do something
}).catch(function (err) {
defaultData = true;
clientService.getClients(defaultData)
.then(function (res) {
//do something
}).catch(function (err) {
console.log(err)
});
});
or this is a bad way ?
Be sure to return the new promise to the catch handler. The code will then chain properly and it avoids nesting:
clientService.getClients({defaultData:false})
.catch(function (err) {
return clientService.getClients({defaultData: true})
}).then(function (res) {
//return something
}).catch(function (err) {
console.log(err)
//IMPORTANT re-throw err
throw err;
});
This issue not bad but I think you have to find reason of the failure and on the furniture do some action.
the best is that you have to create an error handler like:
errorHandler(error:any){
///.....
}
In this method you should check status code of response, for instance if it is 500, you can't call again. or some thing like this.

Best practices in context of asynchronous Javascript when calling functions in functions?

I am trying to call two functions and pass the output of the first function as a parameter into the second.
Function 1:
module.exports.getAllStatisticsByUserId = function(id, callback){
User.findById(id, (err, user) =>{
if(err)
throw err;
if(user)
callback(null, user.statistics);
});
}
Function 2:
module.exports.getGameByStatisticsId = function(id, callback){
Statistics.findById(id, (err, statistics) =>{
if(err)
throw err;
if(statistics)
callback(null, statistics.game);
});
};
I am trying to execute the second method by passing the output of the first method as a parameter but the asynchronous nature of javascript is messing it up. I have tried implementing promises to no avail.
Can anyone suggest some good javascript practices to deal with calling functions asynchronously when they need each other? Any help would be appreciated.
After fixing the issue I mentioned above, you can call them in sequence like this:
module.exports.getAllStatisticsByUserId = function(id, callback){
User.findById(id, (err, user) =>{
if(err) callback(err);
if(user) callback(null, user.statistics);
});
};
module.exports.getGameByStatisticsId = function(id, callback){
Statistics.findById(id, (err, statistics) =>{
if(err) callback(err);
if(statistics) callback(null, statistics.game);
});
};
someService.getAllStatisticsByUserId(id, (err, statistics) => {
if (err || !statistics) {
// handle error
return;
}
someService.getGameByStatisticsId(statistics.id, (err, game) => {
if (err || !game) {
// handle error
return;
}
// handle game
});
});
However, as noted in Mongoose documentation:
When a callback function is not passed, an instance of Query is returned, which provides a special query builder interface.
A Query has a .then() function, and thus can be used as a promise.
So you can simply rewrite the calls like this:
someService.getAllStatisticsByUserId(id).then(statistics =>
someService.getGameByStatisticsId(statistics.id)
).then(game => {
// handle game
}).catch(err => {
// handle error
});
or convert it into an async/await function:
async function getGameByUserId(id) {
try {
const statistics = await someService.getAllStatisticsByUserId(id);
const game = await someService.getGameByStatisticsId(statistics.id);
// handle game
} catch (error) {
// handle error
}
}
Note that an async function always returns a Promise, so you must await it or chain it with a .then() to ensure completion of the query and resolve the returned value, if any.
It looks like you should be able to write:
getAllStatisticsByUserId("me", (err, stats) => {
getGameByStatisticsId(stats.id, (err, game) => {
console.log(game);
});
});
Here's how it would look if these functions returned promises instead.
getAllStatisticsByUserId("me")
.then(stats => getGameByStatisticsId(stats.id))
.then(game => console.log(game))
Even better, if you're able to use a version of Node that supports async/await then you could write.
let stats = await getAllStatisticsByUserId("me");
let game = await getGameByStatisticsId(stats.id);
console.log(game);
This would mean slightly rewriting the original functions (unless User.findById and Statistics.findById already return promises).
module.exports.getAllStatisticsByUserId = function(id, callback){
return new Promise((resolve, reject) => {
User.findById(id, (err, user) =>{
if(err) return reject(err);
return resolve(user.statistics);
});
});
}

How to decouple and encapsulate long Node.js method?

I have the following method, simplified:
var component = require('../../component.js')
exports.bigMethod = function (req, res) {
var connection = new sql.Connection(process.config.sql, function (err) {
new sql.Request(connection)
.input('input', sql.VarChar, 'value')
.execute('dbo.sp1')
.then(function (data) {
if(data[0].length === 0) {
res.status(500).send({ message: 'Some Error' });
}
// set some local variable I'll use later: localVar
new sql.Request(connection)
.input('input1', sql.Int, req.query.input1)
.input('input2', sql.Int, req.query.input2)
.input('input3', sql.DateTime2, req.query.input3)
.input('input4', sql.DateTime2, req.query.input4)
.execute('dbo.sp2')
.then(function (recordset) {
json2csv( { data: recordset[0] }, function(err, data) {
if (err) {
res.status(500).json(err);
}
fs.writeFile(localVar.path, data, function (err) {
if (err) {
res.status(500).json(err);
}
try {
var email = new component.Email();
email.set_callback(function (error) {
if (error) {
res.status(500).send({ message: 'Another error' });
}
res.jsonp([]);
});
email.send(
localVar,
{
filename: localVar.name,
path: localVar.path
}
);
} catch (e) {
res.status(500).send({ message: 'Another Error' });
}
})
});
})
.catch(function (err) {
res.status(500).send({ message: 'Another Error' });
});
})
.catch(function (err) {
res.status(500).send({ message: 'Another Error' });
});
});
};
As you can see, it's a long method, which is an anti-pattern. Furthermore, the call to sp2 is actually a duplication: there's another component that makes a similar call, only returning the result of json2csv directly.
Now, I'm not so sure how to go about dividing this method to fully take advantage of reusability. Should I encapsulate the database calls in functions returning Promises? Should I use some currying?
Thanks.
Dividing your code into different functions should be your top priority — that function alone is handling way too many things which could be easily divided.
You can make your code function much cleaner by dividing through callbacks and promises, as you already suspected.
Callback example:
new sql.Connection(..., handleSQLConnection);
handleSQLConnection(error) { ... }
Promise example:
doSomething
.then((a) => doOtherStuff(a))
.then((b) => doSmthElse(b));
doOtherStuff(a) { ... }
doSmthElse(b) { ...}
Nonetheless, refactoring is very opinionated. But you should try to avoid God functions, and instead write functions that do one thing but they do it well.

Categories

Resources