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 :-)
Related
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.
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(...);
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);
I call storage.createTask and use BlueBird's promises to get the returned value. This works fine when I do new Promise() and resolve the promise using either resolve(something) or reject(error). However, Promise.reject(new Error('some error')) causes cannot read property 'then' of undefined.
According to the docs, Promise.reject
Creates a promise that is rejected with the given reason
. Isn't this similar to reject(error), which works fine?
What's the difference between Promise.resolve/Promise.reject and doing new Promise? When should we use one over the other?
//server.js
// returning Promise.reject causes
// Cannot read property 'then' of undefined
storage.createTask(task).then(function(id) {
task.id = id;
reply(task);
}, function(error) {
console.log(error);
reply(error);
});
// storage.js
function _create(task) {
return new Promise(function(resolve, reject) {
var id = shortid.generate();
Task.create({
id: id,
content: task.content,
deadline: task.deadline,
milestone_id: task.milestone_id,
}).catch(function (error) {
reject(error); // works ok
}).then(function() {
resolve(id); //works ok
});
});
}
module.exports = {
createTask: function(task) {
if (task.milestone_id != null ) {
Milestone.isExist(task.milestone_id).then(function(exists) {
if (!exists) {
return Promise.reject(new Error('some error'));
}
return _create(task);
});
} else {
return _create(task);
}
}
Your createTask() function does not return anything; you need to return the promise created by Milestone.isExist().
Update
Here's how I would rewrite the function:
createTask: function(task) {
if (task.milestone_id == null ) {
return Promise.reject(new Error('null id'));
}
return Milestone.isExist(task.milestone_id).then(function(exists) {
if (!exists) {
return Promise.reject(new Error('some error'));
}
return _create(task);
});
}
With this rewrite, you are always returning a promise from createTask(), so you can safely chain off of it.
I'm using the following two pieces of code :
Store.addUser(newUserInfo).then(function(firstResult) {
Store.getUserList().then(function(result){
console.log('this side');
console.log(result);
io.sockets.emit('userAdded', {
userMapByUserId: result
});
}, function(error) {
console.log('List of users could not be retrieved');
console.log(error);
io.sockets.emit('userAdded', {
userMapByUserId: []
});
}
);
}, function(rejection) {
socket.emit('userNotAdded', {
userId: -1,
message: rejection.reason,
infoWithBadInput: rejection.infoWithBadInput
});
});
and in Store :
var addUser = function(newUserInfo) {
var validationResult = Common._validateUserInfo(newUserInfo);
if (validationResult.isOK) {
return keyValueExists('userName', newUserInfo.userName).then(function(userNameAlreadyExists) {
if (userNameAlreadyExists) {
validationResult = {
isOK: false,
reason: 'Username already exists',
infoWithBadInput: 'userName'
};
return Promise.reject(validationResult);
} else {
var newUserId = generateUserId();
//TODO: change it somehting more flexible. e.g. a predefined list of attributes to iterate over
var newUser = {
'userName': newUserInfo.userName,
'password': newUserInfo.password,
'userId': newUserId,
'lastModificationTime': Common.getCurrentFormanttedTime(),
'createdTime': Common.getCurrentFormanttedTime()
};
var user = new User(newUser);
user.save(function(err) {
if (err) {
console.log(err);
console.log('There is a problem saving the user info');
return Promise.reject('There is a problem saving the user info');
} else {
console.log('A new user added: ');
console.log(newUser);
//return getUserList();
return Promise.accept(newUser);
}
});
}
});
} else {
return Promise.reject(validationResult);
}
};
But in the first code , when I do Store.addUser(newUserInfo) it always runs the first function (resolve function) which shouldn't be the case if we do return Promise.reject() in addUser. Any idea on why this happens ?
You've got two return statements too few, two too much, and are overlooking a non-promisified function call.
Store.addUser(newUserInfo).then(function(firstResult) {
return Store.getUserList().then(function(result){
// ^^^^^^
…
This one is not really problematic, as you don't chain anything after the resulting promise, but it shouldn't be missed anyway.
…
return keyValueExists('userName', newUserInfo.userName).then(function(userNameAlreadyExists) {
if (userNameAlreadyExists) {
…
} else {
…
var user = new User(newUser);
user.save(function(err) { … });
// ^^^^
}
});
In this then-callback, you are not returning anything from your else branch. The promise is immediately fulfilled with undefined, and the ongoing save call is ignored - your promises don't know about it, so they can't await it. That's why Store.getUserList() that follows next in the chain doesn't see the changes; they're not yet stored.
It's also the reason why your Promise.reject inside that callback is ignored, and why Promise.accept never caused any problems.
You will need to create a new promise for the result of the save invocation here (so that you actually can return it):
…
var user = new User(newUser);
return new Promise(function(resolve, reject) {
user.save(function(err) {
if (err) {
console.log(err);
console.log('There is a problem saving the user info');
reject('There is a problem saving the user info');
} else {
console.log('A new user added: ');
console.log(newUser);
resolve(newUser);
}
});
}); // .then(getUserList);