Promise Chain doesn't seem to continue - javascript

I'm trying to get my promise chain to work in my express post, but can't seem to figure out why it won't work, even though I've done this before.
I added many logs to follow where it stops, and it seems to stop after the validation resolve, does not continue
Promise:
router.post('/auth/token', function (req, res) {
var schema = require('./schema/token.js');
var data = {
username: req.body.username,
password: req.body.password
};
new Promise(function (resolve, reject) {
logger.info(`Validating request..`);
return validator.validate(schema, data);
}).then(function () {
logger.info(`Getting token..`);
return authentication.getToken(data.username, data.password);
}).then(function (result) {
logger.info(`Received token..`);
res.send(result);
}).catch(function (err) {
logger.info(`Unable to receive token..`);
res.send(err);
})
})
Validator.js:
module.exports.validate = function (schema, data) {
return new Promise(function(resolve, reject){
logger.info(`Loading schema..`);
if (!schema) {
logger.info(`Missing schema, rejecting promise..`);
return reject(new Error('Missing schema'));
}
const ajv = new Ajv({ v5: true, allErrors: true });
logger.info(`Compling schema..`);
const validate = ajv.compile(schema);
logger.info(`Validating schema..`);
const valid = validate(data);
if (!valid) {
const errors = validate.errors.map((error) => `${error.dataPath.replace('.', '')}: ${error.message}`);
const err = new Error(errors);
return reject(err);
}
logger.info(`Valid schema.. resolving..`);
return resolve();
})
}
When I run this.. the logs say the following:
info: Validating request..
info: Loading schema..
info: Compling schema..
info: Validating schema..
info: Valid schema.. resolving..
No longer continues, it should continue to the next promise, now if I change the first promise and force a resolve and reject, it'll work but as far as I know, that should not be required as the validate returns a promise and I'm not getting any errors
Any ideas?

Don't create a new promise, use the one from validate; see *** below:
router.post('/auth/token', function (req, res) {
var schema = require('./schema/token.js');
var data = {
username: req.body.username,
password: req.body.password
};
logger.info(`Validating request..`); // ***
validator.validate(schema, data) // ***
.then(function () {
logger.info(`Getting token..`);
return authentication.getToken(data.username, data.password);
}).then(function (result) {
logger.info(`Received token..`);
res.send(result);
}).catch(function (err) {
logger.info(`Unable to receive token..`);
res.send(err);
})
})
The issue is you never resolve the new promise you create. But since there's no good reason to create a new promise when you already have one, the solution is to use the one you have.

Related

How to resolve or reject a promise from the result of another promise?

Really sorry if this has been answered, I've searched everywhere and can't find the exact problem I'm facing.
Take this as the example:
const fetchData = (email, password) => new Promise(async (resolve, reject) => {
await axios.post('https://api.something.com', {
email: email,
password: password,
},
{
headers: {
'Content-Type': 'application/json',
}
})
.then(res => {
cookie = res.headers['set-cookie'];
})
.catch(err => {
return reject('Login failed');
});
await axios.get('https://api.something.com', {
headers: {
'cookie': cookie
}
})
.then(res => {
data = res;
})
.catch(err => {
return reject('Failed to retrieve something');
});
return resolve(data);
});
If the login credentials are incorrect, the 'Login failed' reject is sent but the script will keep on running and there will be an additional error message saying that cookie isn't set. I want to completely stop the script in the first catch.
I could use throw new Error('Login failed') and that would stop the script completely but I don't feel like that's the right answer and also because it makes me wonder what else could I use to resolve the promise (for other purposes) and still don't let the script continue running.
I'm also not interested in nesting functions, to avoid promise-callback christmas tree-like hell.
Am I making sense?
Because you use async/await you don't need create a new Promise and instead of then/catch use try/catch;
const fetchData = async (email, password) => {
let cookie;
try {
const res = await axios.post(
"https://api.something.com",
{
email,
password
},
{
headers: {
"Content-Type": "application/json",
},
}
);
cookie = res.headers["set-cookie"];
} catch (err) {
throw new Error("Login failed");
}
let data;
try {
const res = await axios.get("https://api.something.com", {
headers: {
cookie
},
});
data = res;
} catch (err) {
throw new Error("Failed to retrieve something");
}
return data;
};
If you want to use the Promise API instead of async/await you can chain the promises: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#chaining

How to set status code to 404 whenever a promise is rejected

I am verifying login functionality by using promises and chaining them. When user enters invalid password, rejecting the data or else resolving it. At the end i am verifying if user is logged in successfully by chaining these methods.
let verifyData = (req, res) => {
return new Promise((resolve, reject) => {
if (req.body.name) {
userModel.findOne({ name: req.body.name }).exec((err, result) => {
if (result) {
resolve(result);
} else {
reject(err);
}
});
} else {
let apiResponse = response.generate(
false,
null,
404,
"Mandatory fields missing, Please provide your userId and password"
);
reject(apiResponse);
}
});
};
Is there a way i can send status code as 404 whenever data is rejected?
This is causing an issue as currently i am getting 200 status code for rejected data
verifyData(req, res).then(validatePassword).then((result) => {res.send(result);})
.catch((err) => {
res.send(err);
});
see: https://expressjs.com/en/api.html#res.status
verifyData(req, res).then(validatePassword).then((result) => {res.send(result);})
.catch((err) => {
const status = err.STATUS_PROP; // get status from apiResponse
// or
const status = 400
res.status(status).send(err);
});

How to migrate my mongoose PROMISE chain transactions to ASYNC / AWAIT flow?

I created an API that integrate database responses in a promise flow, but I think the interpretation of the code is complex and I believe that async / await approach could improve both understanding and the code itself.
The API is built in NodeJS using mongoose 5.6.1 and express 4.17.1.
Can you help me in improve this?
Below is the API that I want to improve:
/** New employee */
router.post('/', (req, res) => {
let { idCompany, name, departament } = req.body;
let _id = mongoose.Types.ObjectId(); // Generating new MongoDB _ID
let employeeCreated;
const promise1 = new Promise((resolve, reject) => {
// Querying by document '$oid'
Companies.findOne({ _id: idCompany }, (err, company) => {
// Error returned
if (err) reject({ error: "Invalid request, something went wrong!" });
// Invalid data received
if (!company) reject({ error: "Unauthorized action!" });
// Everything OK
resolve(company);
});
})
.then(company => {
if(company) {
const promise2 = new Promise((resolve, reject) => {
Employees.create({ _id, idCompany, name, departament }, (err, employee) => {
// Error returned
if (err) reject({ error: "Invalid request, something went wrong!", err });
// Everything OK
employeeCreated = employee;
resolve(company);
});
})
return promise2;
}else reject({ error: "Company not found!" });
})
.then(company => {
let { name: companyName, address, email, tel, employees } = company;
employees.push(_id);
const promise3 = new Promise((resolve, reject) => {
Companies.findByIdAndUpdate(
{ _id: idCompany },
{ $set: { _id: idCompany, name: companyName, address, email, tel, employees } }, // spotlight
{ new: true },
(err, company) => {
// Something wrong happens
if (err) reject({ success: false, error: "Can't update company!" });
// Everything OK
resolve(company);
}
);
});
return promise3;
});
promise1
.then(() => res.json({ success: true, employeeCreated }))
.catch(err => res.status(400).json({ error: "Invalid request, something went wrong!", err }));
});
Regards.
One key to using promises with mongoose, is using the exec method:
Your code could then look something like this (not tested):
router.post('/', async (req, res) => {
try {
const { idCompany, name, departament } = req.body;
const _id = mongoose.Types.ObjectId();
const company = await Companies.findOne({ _id: idCompany }).exec();
const employeeCreated = await Employees.create({ _id, idCompany, name, departament });
const { name: companyName, address, email, tel, employees } = company;
employees.push(_id);
await Companies.findByIdAndUpdate(
{ _id: idCompany },
{ $set: { _id: idCompany, name: companyName, address, email, tel, employees } }, // spotlight
{ new: true }).exec();
res.json({ success: true, employeeCreated });
} catch(err) {
res.status(400).json({ error: "Invalid request, something went wrong!", err });
}
});
You could throw some specific custom errors in the try block if you find that necessary.
You could simply make the functions where your promises are running async and so, you could await for the promises to resolve.
For example, in your route use this:
router.post('/', async (req, res) => {
and then when performing an async operation, use this:
const company = await Companies.findOne({ _id: idCompany }).exec();
Also, I would suggest you to wrap this with try and catch statments
Hope it helps!

Wait for second promise

Right now I'm learning promises and want to get a token from a webserver, which also uses a promise. I tried it before without a promise but it's also not working.
This is the first block.
promise = new Promise(resolve => {
let accessToken = helper.getAccessToken(baseUrl);
let userCollection = helper.createCollection("user", db);
let excersizeCollection = helper.createCollection("excercise", db);
resolve({
accessToken: accessToken,
database: {
userCollection: userCollection,
excersizeCollection: excersizeCollection
}
});
});
promise
.then(promises => {
console.log("my token" + promises.accessToken);
new nceDefaultbotCommands(bot, promises.accessToken, baseUrl);
new botComamnds(bot, promises.database);
let userController = new reqUserController(
baseUrl,
0,
promises.accessToken
);
bot.start();
})
.catch(() => {
console.log("error");
});
Only the access token is not working, this is in my helper class and it looks like this.
static getAccessToken(baseUrl) {
let promise = new Promise(resolve => {
request.post(
{
url: baseUrl + "/token",
body: {
credentials: {
user: "USER",
password: "PW"
}
},
json: true //// Automatically parses the JSON string in the response
},
(error, response, body) => {
if (error) console.log("error");
if (!error && response.statusCode === 200) {
resolve({ token: body.token });
}
}
);
});
promise.then(resolve => {
console.log(resolve.token);
return resolve.token;
});
}
I get the access token but normally after the then of the first promise.
Thanks in advance.
You're fulfilling your first promise with an object, so that object is the fulfillment value (the promise isn't magically resolved to the promises that are values of properties on that object).
There's no reason to use new Promise when you have a promise or promises to work with, just chain off them; in this case, via Promise.all:
Promise.all([
helper.getAccessToken(baseUrl),
helper.createCollection("user", db),
helper.createCollection("excercise", db)
])
.then(([accessToken, userCollection, exersizeCollection]) => { // Note the destructuring
console.log("my token" + accessToken);
new nceDefaultbotCommands(bot, accessToken, baseUrl);
new botComamnds(bot, {userCollection, exersizeCollection});
let userController = new reqUserController(baseUrl, 0, accessToken);
bot.start();
})
.catch(/*...*/);
Note I corrected the spelling of "exercise," which may require changes to your botCommands constructor.
You do not return your Promise:
static getAccessToken(baseUrl) {
let promise = new Promise(resolve => {
...
});
return promise.then(resolve => {
console.log(resolve.token);
return resolve.token;
});
}
Then I think you need to handle it like Promise, somewhat like:
promise = new Promise(resolve => {
let accessToken = helper.getAccessToken(baseUrl).then(token => {
let userCollection = helper.createCollection("user", db);
let excersizeCollection = helper.createCollection("excercise", db);
resolve({
accessToken: accessToken,
database: {
userCollection: userCollection,
excersizeCollection: excersizeCollection
}
});
});
});

Modular promises and Promise.all()

I have a bunch of functions that return promises that I want to make generalized, and so I write them like this:
function checkWebpageForReference(data){
//checks a webpage for the reference in html
var promise = new Promise(function(resolve,reject){
fetchUrl(data.url, function(err, meta, body){
if (err) { reject(err); } else {
console.log(body)
if (body.toString().indexOf(data.text) !== -1){
resolve(data);
} else {
reject("Could not find quote");
}
}
});
});
return promise;
}
function takeScreenshot(data){
//takes a screenshot of a webpage and saves it to the file system
//TODO: Mouse coordinates
data.id = shortid.generate();
data.filename = data.id+'.png';
var promise = new Promise(function(resolve,reject){
webshot(data.url, data.filename, { shotOffset: {left: data.mouseX, top: data.mouseY} }, function(err) {
if (err) { reject(err); } else {
resolve(data);
}
});
});
return promise;
}
function uploadReferencePictureToS3(data){
//uploads a picture to S3
var promise = new Promise(function(resolve, reject){
s3.putObject({
ACL: 'public-read',
Bucket: S3_BUCKET,
Key: data.id,
Body: data.picturedata,
ContentType: "image/jpg"
}, function(err) {
if (err) { reject(err); } else {
resolve(data);
}
});
});
return promise;
}
function saveNewReferenceToDb(data){
//saves a new Reference to the database
var promise = new Promise(function(resolve, reject){
new Reference({
_id: data.id,
url: data.url,
text: data.text,
screenshot_url: AWS_S3_URL + data.id,
created_by: "Daniel"
}).save(function(err, saved){
if (err) { reject(err); } else {
data.newReference = saved;
resolve(data);
}
});
});
return promise;
}
function readFile(data){
//reads a file from the file structure and stores it in a variable
var promise = new Promise(function(resolve,reject){
console.log(data);
fs.readFile(data.filename, function(err, picturedata){
console.log(picturedata);
if (err) { reject(err); } else {
data.picturedata = picturedata;
resolve(data);
}
}) ;
});
return promise;
}
function deleteFile(data){
//deletes a file from the file structure
var promise = new Promise(function(resolve, reject){
fs.unlink(data.filename);
resolve(data);
});
return promise;
}
I resolve data in each function because I plan to have a lot of these types of functions, and I don't know the order they'll be called in while chaining:
readfile(somedata)
.then(upload)
.then(delete)
.then(save)
//etc
This works fine until I have to do Promise.all:
Promise.all([
referenceTools.checkWebpageForReference(req.body),
referenceTools.takeScreenshot(req.body)
])
.then(function(results){
utils.readFile(results[1])
.then(referenceTools.uploadReferencePictureToS3)
.then(utils.deleteFile)
.then(referenceTools.saveNewReferenceToDb)
.then(function(data){
res.json(data.newReference);
})
.catch(function(err){
utils.errorHandler(err);
res.send("There was an internal error. Please try again soon.");
});
})
.catch(function(err){
utils.errorHandler(err);
res.send("There was an internal error. Please try again soon.");
});
//my very ugly way of doing it
Using Promise.all().then(upload) gives me errors, because the new promise returned by Promise.all() is an object that contains both resolutions from checkWebpageForReference and takeScreenshot. Essentially, in readFile, I can't access data fields because the resulting promise is [data, data].
Is there a pattern I can follow to help me achieve what I need to do? I need to make the promises modular providing them with as much data as possible.
You can .map() over them like so:
Promise.all(...)
.then(datas => Promise.all(datas.map(upload)));
Since you're on the server side, I highly recommend Bluebird as a drop-in replacement for native Promises. Then you can do:
Promise.all(...)
.map(upload);

Categories

Resources