Passing data in promise chain nodejs - javascript

I have a chain of promises that the first one gets data from an API, and the 2nd one inserts the data into a database.
I am attempting to pass the data from the first promise to the 2nd promise, but it's coming through as undefined.
Here is my code:
var getBalancePromise = function() {
var promise = new Promise(function(resolve, reject) {
poloniexExchange.getBalance({
account: 'all'
}, function(err, response) {
if (err)
console.log(err);
reject(err);
if (!err)
resolve(response); //response is an array
});
}).catch((err) => {
console.log('error');
})
return promise;
};
var updateBalancePromise = function(balanceArray) //balanceArray undefined. This should be the data from the first promise in the chain.
{
var promise = new Promise(function(resolve, reject) {
balanceArray.data.forEach(function(element) {
db.collection('balances').update({
currency: element.currency
}, {
$set: {
amount: element.amount,
shortname: element.shortName
}
}, {
upsert: true
});
});
resolve(true);
console.log('balances updated into database');
});
return promise;
};
getBalancePromise()
.then(updateBalancePromise);
How do I change my code to pass data from first promise to 2nd promise?

You are always rejecting the promise:
if (err)
console.log(err);
reject(err); // This line is always executed
if (!err)
resolve(response); //response is an array
This causes the .catch callback to be triggered (.catch((err) => { console.log('error'); })) which doesn't return anything, so balanceArray is undefined.
First make sure to only reject the promise if there is an error:
if (err) {
console.log(err);
reject(err);
}
Secondly, either rethrow the error in the .catch callback or remove it completely and catch at the top level instead:
getBalancePromise()
.then(updateBalancePromise)
.catch(...);

Related

How to return promise with a return value

getAccomodationCost is a function which is expected to return a promise with a return value. Now It's throwing an error resolve is undefined.
This error message is thrown at line resolve(JSON.parse(JSON.stringify(result))) inside promise then. If i replace keyword resolve with return then Promise.all call in the main function will fail.
Can some one help me to return a promise with a return value JSON.parse(JSON.stringify(result)) from the below function.
var getAccomodationCost = function (req, res) {
var accomodationCostPromise = new Promise(function (resolve, reject)
{
getHospitalStayDuration(req, res, function (duration) {
resolve(duration)
})
})
.then(function (duration) {
hotelModel.aggregate([
//Some logic here
], function (err, result) {
resolve(JSON.parse(JSON.stringify(result)))
})
})
return accomodationCostPromise;
}
//Main function where the above snippet is called
const promise1 = somefunction(req, res);
const accomodationCostPromise = getAccomodationCost(req, res)
Promise.all([promise1,accomodationCostPromise])
.then(([hospitalInfo,accomodationCost]) => {
//Return some json response from here
}).catch(function (err) {
return res.json({ "Message": err.message });
});
If possible have hotelModel.aggregate return a promise. That'd make the code look something like this:
.then(function (duration) {
return hotelModel.aggregate([
//Some logic here
]).then(result => JSON.parse(JSON.stringify(result))) // Not sure why you're stringify/parsing
})
If you cannot modify hotelModel.aggregate to return a promise, you will need to create another promise and return that from .then(function (duration), similar to how you did it for getHospitalStayDuration.
A Promise can only be fulfilled once. resolve() is called twice within function, resolve is not defined within .then(). resolve is defined within Promise constructor executor function. A second Promise should be used within .then().
var getAccomodationCost = function (req, res) {
return new Promise(function (resolve, reject) {
getHospitalStayDuration(req, res, function (duration) {
resolve(duration)
})
})
.then(function (duration) {
return new Promise(function(resolve, reject) {
hotelModel.aggregate([
//Some logic here
], function (err, result) {
if (err) reject(err);
resolve(JSON.parse(JSON.stringify(result)))
})
})
});
}

Promise Chain doesn't seem to continue

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.

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);

How to break promise chain on error

Consider this snippet
fetch(`http://${api.host}:${api.port}/user`)
.then(function(data) {
return data.json();
}, function(err) {
throw new Error(`Couldn\'t fetch user data from server: ${err.message}`);
}).then(function(eparkUser) {
for (var key in eparkUser) {
if (eparkUser.hasOwnProperty(key) && !user.hasOwnProperty(key)) {
user[key] = eparkUser[key];
}
}
done(null, user);
}, function(err) {
throw new Error(`Couldn't parse returned json: ${err.message}`);
}).catch(function(e) {
done(e);
});
Isn't throw supposed to break the chain and trigger .catch ? How to achieve this behaviour? Becauce now both throw are getting executed and I see message:
Error: Couldn't parse returned json: Couldn't fetch user data from server: request to http://localhost:3010/user failed and that not what I want.
P.S. fetch is npm node-fetch module
No, throw does not jump to catch. It does reject the promise, and all error handlers installed on it will be invoked. In your case, that's the error handler installed by the then call. Notice that .catch(handler) is just sugar for .then(null, handler).
Your current code works like
try {
try {
try {
var data = fetch(`http://${api.host}:${api.port}/user`)
} catch(err) {
throw new Error(`Couldn\'t fetch user data from server: ${err.message}`);
}
var eparkUser = data.json();
} catch(err) {
throw new Error(`Couldn't parse returned json: ${err.message}`);
}
for (var key in eparkUser) {
if (eparkUser.hasOwnProperty(key) && !user.hasOwnProperty(key)) {
user[key] = eparkUser[key];
}
}
done(null, user);
} catch(e) {
done(e);
}
To solve your problem, you'll need to nest your handlers, and install the JSON-parse-handler only on that particular promise:
fetch(`http://${api.host}:${api.port}/user`)
.then(function (data) {
return data.json()
.then(function (eparkUser) {
for (var key in eparkUser) {
if (eparkUser.hasOwnProperty(key) && !user.hasOwnProperty(key)) {
user[key] = eparkUser[key];
}
}
return user;
}, function(err) {
throw new Error(`Couldn't parse returned json: ${err.message}`);
});
}, function(err) {
throw new Error(`Couldn\'t fetch user data from server: ${err.message}`);
})
.then(done.bind(null, null), done);

Return another promise from a promise

I have a promise for an object and would like to get a promise for a property of that object. How should I do that?
var user = Q.nfcall(User.findOne, {
_id: userId
});
var accessToken = Q.Promise(function (resolve, reject) {
user.then(function (user) {
if (!user) return reject(new Error('User not found.'));
if (!user.github.accessToken) return reject(new Error('Access token not found.'));
return resolve(user.github.accessToken);
}, function(err) {
return reject(err);
});
});
This is what I tried so far, but I'm not sure if its the best (or most correct) way.
Do not use the deferred antipattern1! There's no need to use Promise constructor, .then already returns you a promise for the result of its callback:
var accessToken = user.then(function(user) {
if (!user) throw new Error('User not found.');
if (!user.github.accessToken) throw new Error('Access token not found.');
return user.github.accessToken;
});
[1]: You've seen yourself how errorprone it is :-)

Categories

Resources