Modular promises and Promise.all() - javascript

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

Related

Convert csv to JSON return empty json if called from another function NodeJS

I do not know what should be the problem but I currently implemented function that convert csv file to JSON and print result into console.log
function looks like :
var Converter = require("csvtojson").Converter;
var converter = new Converter({delimiter: ';'});
function convertToJSON() {
converter.fromFile('data.csv', function(err, result) {
if (err) {
console.log(err);
}
var data = result;
console.log(result);
});
}
and when I call it this way in server.js it return JSON to console
convertToJSON();
but when I would like to call this function from app.get by REST GET it return allways empty object.
app.get("convertToJSON", function(req,res){
convertToJSON();
})
I dont know what should be problem, why it is not work inside get call. There is no error during execution.
Your code has become asynchronous use a promise in that funtion.
Your file is taking more time to load when called inside app.get.
function convertToJSON() {
var promise1 = new Promise(function(resolve, reject) {
converter.fromFile('data.csv', function(err, result) {
if (err) {
reject(err);
}
var data = result;
resolve(result);
});
});
return promise1;
}
As we're using Promise, we need to handle it properly. Please take a look at this reference for more details.
Try this...
var Converter = require("csvtojson").Converter;
var converterObj = new Converter({delimiter: ';'});
function convertToJSON() {
return new Promise((resolve, reject) => {
converterObj.fromFile('data.csv', (err, result) => {
if (err) {
reject(err);
}
resolve(result);
});
});
}
convertToJSON()
.then((result) => {
console.log(result);
})
.catch((error) => {
console.log(error);
});

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.

Passing data in promise chain nodejs

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

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

Promise returns early with a for loop

I'm new to Promises so I might be doing something stupid here, but I can't seem to figure it out.
Just so I know I'm on the right path, a bit of information upfront. I have an authenticate method which returns a promise:
APIWrapper.prototype.authenticate = function() {
var self = this;
return new Promise(function(resolve, reject) {
request({
uri: self.httpUri + '/auth/authenticate',
method: 'GET',
headers: {
auth_user: self.user,
auth_pass: self.pass,
auth_appkey: self.appkey
}
}, function(err, res, body) {
if (err) return reject(err);
self.parser.parseXML(body, function(err, result) {
if (err) return reject(err);
if (result.error) { return reject(result.error) }
self.token = result.auth.token[0];
return resolve(result);
});
});
});
};
I chain this with .getDashboards() like this:
wrapper.authenticate().then(function() {
wrapper.getDashboards();
}).then(function(result) {
console.log('result', result);
});
.getDashboards() also returns a promise:
APIWrapper.prototype.getDashboards = function() {
var self = this;
return new Promise(function(resolve, reject) {
request({
url: self.httpUri + '/user/dashboard',
method: 'GET',
headers: {
auth_appkey: self.appkey,
auth_token: self.token
}
}, function(err, res, body) {
if (err) { return reject('Could not connect to the API endpoint'); };
self.parser.parseXML(body, function(err, data) {
var dashboards = [];
if(err) { return reject(err); }
if(data.error) { return reject(data.error); }
for(var i = 0; i < data.Dashboards.Dashboard.length; i++) {
dashboards.push(self.getDashboard(data.Dashboards.Dashboard[i]));
}
// returns early here
resolve(dashboards);
});
});
});
};
With the .getDashboard() method like this at the moment:
APIWrapper.prototype.getDashboard = function(db) {
var dashboard = {};
dashboard.title = db.Title[0];
dashboard.id = db.$.id;
console.log(dashboard);
return dashboard;
};
What happens with this code is that it returns the result before it returns the dashboards. I suspect the resolve() in .getDashboards() doesn't wait for the for loop to finish? Do I need to use promises in the .getDashboard() method as well, or how would I wait for it to finish before resolving my .getDashboards() promise?
Output:
> result undefined
{ title: 'Dashboard 1', id: '3271' }
{ title: 'Dashboard 2', id: '3272' }
{ title: 'Dashboard 3', id: '3273' }
I'm using this Promise implementation at the moment: https://github.com/then/promise
You need to return the promise to have it chained :
wrapper.authenticate().then(function() {
return wrapper.getDashboards();
}).then(function(result) {
console.log('result', result);
});
In your case, it can be simplified as
wrapper.authenticate()
.then(wrapper.getDashboards)
.then(function(result){
console.log('result', result);
});
You also don't seem to handle errors. The then library seems very raw on this point, so you should probably add a second argument :
wrapper.authenticate()
.then(wrapper.getDashboards, onAuthenticateError)
.then(function(result){
console.log('result', result);
}, onDashboardError);

Categories

Resources