I'm trying to fill some local data resolving a series of remote calls.
When every promise is resolved, I load the data and proceed.
The method $q.all( [] ) does exactly this:
$q.all([
this.getUserInfo(11)
.then(function (r) {
results.push(r)
}),
this.getUserConns()
.then(function (r) {
results.push(r)
}),
this.getUserCtxs()
.then(function (r) {
results.push(r)
})
])
.then(function () {
console.log(results)
})
Problem is, this code is not resilient.
If any of these call fails, nobody gets the fish!
Wrapping the calls in a try/catch statement, simply causes $q.all() to entirely ignore the entry, even when not failing (note the console.log in the func)...
$q.all([
this.getUserInfo2(11)
.then(function (r) {
results.push(r)
}),
function () {
try {
this.getUserGroups()
.then(function (r) {
console.log(r)
results.push(r)
})
}
catch (err) {
console.log(err)
}
},
])
.then(function () {
console.log(results)
})
Output:
[Object]
Any hint on how I could wrap this to be resilient?
Thanks to #dtabuenc, I've gone one step further.
Implementing the error callback, I can avoid the breaking of the chain, and push the values of the resolved promises.
However, a nasty Exception is still displayed on the console...
How can I get rid of that if I cannot try/catch on async requests?
Caller code
return $q.all([
this.getUserInfo(user_id)
.then(function (r) {
results['personal_details'] = r
}),
this.getUserConns()
.then(
function (r) {
results['connections'] = r
},
function(err) {
console.log(err)
})
])
.then(function () {
return (results)
})
Callee code (inject with an exception)
getUserConns: function() {
return __doCall( ws.getUserConnections, {} )
.then( function(r) {
// very generic exception injected
throw new Error
if (r && r.data['return_code'] === 0) {
return r.data['entries']
}
else {
console.log('unable to retrieve the activity - err: '+r.data['return_code'])
return null
}
})
},
This will work but also push the errors to the array.
function push(r) {
results.push(r);
}
$q.all([
this.getUserInfo(11).then(push).catch(push),
this.getUserConns().then(push).catch(push),
this.getUserCtxs().then(push).catch(push)
])
.then(function () {
console.log(results);
})
You should also improve your understanding of promises, you never should use try-catch with promises - when using promises, you use the .catch() method (with everything else being implicitly a try). This works for normal errors as well as asynchronous errors.
If you want to totally ignore the errors:
function push(r) {
results.push(r);
}
function noop() {}
$q.all([
this.getUserInfo(11).then(push).catch(noop),
this.getUserConns().then(push).catch(noop),
this.getUserCtxs().then(push).catch(noop)
])
.then(function () {
console.log(results);
})
I think it's easier to do :
$q.all([
mypromise1.$promise.catch(angular.noop),
mypromise2.$promise.catch(angular.noop),
mypromise1.$promise.catch(angular.noop)
])
.then(function success(data) {
//.....
});
I'm not sure what you mean by resilient. What do you want to happen if one of the promises fails?
Your try-catch won't work because the promise will fail asynchronously.
You can however pass in an error handler as the second parameter to the then() call and do whatever you wish there.
Same issue here. For those of you with for loops: inside a then response:
var tracks = [];
var trackDfds = [];
for(var i = 0; i < res.items.length; i++){
var fn = function () {
var promise = API.tracks(userId, res.items[i].id);
return promise.then(function (res) {
if (res.items.length) {
tracks.push(res.items);
}
}).catch(angular.noop);
};
trackDfds.push(fn());
}
$q.all(trackDfds)
.then(function (res) {
console.log(tracks);
});
#Esailija's answer seems like a workaround to a problem.
You can't resolve the problem outside the main contributor to the problem: $q.
It seems a bit wiser to have reject callbacks for each then (2nd argument) and in there to insert $q.reject(...).
Example:
$q.all([
this.getUserInfo(11).then(
function (response) { // UI data preparation for this part of the screen },
function (response) {
$q.reject(response);
}
),
// ...
])
.then(
function () {
// all good
},
function () {
// at least one failed
}
)
This is particularly indicated when the UI model depends on all ajax calls.
Personally I think this is the safe way to proceed anyway, because most of the times you do want to push some server messages to some toast component on the reject callbacks, or alert the user in some way (queuing 7 ajax calls doesn't mean you can't show anything because 1 failed - it means you won't be able to show some region of the screen - that needs a specialized feedback to the user).
Related
Scenario
I'm trying to do multiple it specs on a single external load rather than have the external data loaded EVERY time.
Question
How can I do this with a single call of getExternalValue while still keeping my it definitions?
Ideas
Currently I'm doing all the expects in a single it block. I've also thought about storing the loaded value before my tests but then I'd have to find another way to make jasmine wait until the value is loaded.
Code
function getExternalValue(callback) {
console.log("getting external value");
setTimeout(function() {
callback(true);
}, 2000);
return false;
}
describe("mjaTestLambda()", function() {
it("is truthy", function(done) {
let truthy;
truthy = getExternalValue(function(bool) {
truthy = bool;
expect(truthy).toBeTruthy();
done();
});
});
it("is falsy", function(done) {
let truthy;
truthy = getExternalValue(function(bool) {
truthy = bool;
expect(!truthy).toBeFalsy();
done();
});
});
});
How can I do this with a single call of getExternalValue while still
keeping my it definitions?
Use beforeEach() or beforeAll() to get the resolved value. Personally I suggest beforeEach() as it will reset the value for each test and helps ensure a clean setup for each one.
I noticed your function has a callback parameter. Async/await is a useful pattern that works best when (1) you're writing async/await functions or (2) your functions return a Promise. If you need to keep the callback parameter, let me know and I'll update the following:
// returns Promise
function getExternalValue() {
return new Promise((resolve, reject) => {
console.log("getting external value");
setTimeout(() => {
resolve(true);
}, 2000);
});
}
describe("mjaTestLambda()", () => {
let value;
beforeAll(() => {
return getExternalValue()
.then((v) => { value = v; });
});
it("is truthy", () => {
expect(v).toBeTruthy();
});
it("is not falsy", () => {
expect(!v).toBeFalsy();
});
});
I am using Chai http and the promise. The following test should fail, but it passes without ever calling the then function. If I add the done parameter to wait for the async function to finish, it fails (correctly). Am I doing something wrong?
it('Returns the correct amount of events', function() {
chai.request(app)
.get('/api/events/count')
.then(function(res) {
throw new Error('why no throw?');
expect(res).to.have.status(200);
expect(res).to.be.json;
})
.catch(function(err) {
throw err;
});
});
When you forget to return promise your test is evergreen. So, you just need to return promise to make it work:
it('Returns the correct amount of events', function() {
return chai.request(app)
.get('/api/events/count')
.then(function(res) {
throw new Error('why no throw?');
expect(res).to.have.status(200);
expect(res).to.be.json;
})
.catch(function(err) {
return Promise.reject(err);
});
});
I need to iterate over result set from the sequelize result. I have a code that works, but I think something is wrong with it, and it should not be done this way.
I have a feeling this is a blocking code.
This is the code that works:
models.Project.findAll({
where: {ProjectId: projectId}
})
.then(function (projects) {
//Iteration is here
var projectList = [];
projects.forEach(function (res) {
projectList.push(res.dataValues.PartId);
});
//then bulk lookup for the result
models.Customers.findAll({
where: {'id': {in: [projectList]}}
}).then(function (customers) {
reply(customers).code(200);
}, function (rejectedPromiseError) {
reply(rejectedPromiseError).code(401);
});
//reply(sameparts).code(200);
}, function (rejectedPromiseError) {
reply(rejectedPromiseError).code(401);
});
This iteration parts is done:
projects.forEach(function (res) {
projectList.push(res.dataValues.PartId);
});
And then this code is executed as another query:
models.Customers.findAll({
where: {'id': {in: [projectList]}}
}).then(function (customers) {
reply(customers).code(200);
}, function (rejectedPromiseError) {
reply(rejectedPromiseError).code(401);
});
How can I rearange it so it uses Promises?
EDIT (Possible solution):
After playing a bit, I believe I have implemented promises.
getAll: function (request, reply) {
var projectId = request.params.projectid;
var promises = [];
var post;
models.SamePart.findAll({
where: {ProjectId: projectId}
})
.then(function (sameparts) {
//Iteration is here
sameparts.forEach(function (res) {
promises.push(
Promise.all([
models.Parts.findAll({where: {id: res.dataValues.PartId}})
]))
});
//Bulk lookup for the parts that were marked as identical
return Promise.all(promises);
}).then(function (completepartslist) {
reply(completepartslist).code(200);
});
Is this a correct approach? It seems that completepartslist contains many unwanted objects, including Promise stuff. How can I flatten it, so to avoid complex for loops?
If you are using .then(), then you are, in all probability, already using promises.
Your original, working code doesn't appear to be blocking.
Your final getAll() looks like it should simplify to :
getAll: function (request, reply) {
models.SamePart.findAll({
where: { ProjectId: request.params.projectid }
}).then(function (sameparts) {
return Promise.all(sameparts.map(function (res) {
return models.Parts.findAll({ where: { id: res.dataValues.PartId } });
}));
}).then(function (completepartslist) {
reply(completepartslist).code(200);
});
}
However, you need to add error handling back in.
even more simplified
getAll: function (request, reply) {
models.SamePart.findAll({
where: { ProjectId: request.params.projectid }
}).reduce(function (completepartslist, sameparts) {
return models.Parts.findAll({ where: { id: sameparts.PartId } }).
then(function(res){
completepartslist.concat(res)
});
}), []);
}).then(function (completepartslist) {
reply(completepartslist).code(200);
});
}
I am trying to create a simple Cloud Job on parse.com but it doesn't behave as expected.
The job returns without error but in the process I am making a find query that seems to be thrown out to the void. There is no error, my console.log are visible before executing query.find() but after that nothing... The query seems to fail silently.
Here is my code:
Parse.Cloud.job("maintenanceJob", function(request, status) {
return performMaintenanceTasks().then(function() {
status.success("Parse Job done");
}, function(errors) {
status.error(tools.prettifyError(errors));
});
});
function performMaintenanceTasks ()
{
// If we have more than NB_MAX_ITEMS objects in Items, let's delete some
var query = new Parse.Query(Items);
return query.count({
success: function(count) {
if(count > NB_MAX_ITEMS) {
return deleteOldItems(1); // 1 is used for test
}
return Parse.Promise.as("Nothing to do.");
},
error: function(error) {
return Parse.Promise.error(error);
}
});
}
function deleteOldItems(nbToDelete) {
// (...)
var query = new Parse.Query(Items);
query.ascending("createdAt");
query.limit(nbToDelete);
query.include("rawData");
console.log("I am visible in console, but NOTHING AFTER ME. query.find() seems to return immediately");
return query.find({
success: function (results) {
// I never pass here
var promise = Parse.Promise.as();
_.each(results, function (item) {
// For each item, extend the promise with a function to delete it.
promise = promise.then(function () {
var rawData = item.get("rawData");
// If we have a rawData, delete it before Item
if (rawData && rawData.id) {
return rawData.destroy({
success: function (theObj) {
return item.destroy({
success: function (anotherObj) {
// I never pass here
return Parse.Promise.as();
},
error: function (anotherObj, error) {
// I never pass here
return Parse.Promise.as();
}
});
},
error: function (theObj, error) {
// I never pass here
return Parse.Promise.as();
}
});
} else {
return item.destroy({
success: function (anotherObj) {
// I never pass here
return Parse.Promise.as();
},
error: function (anotherObj, error) {
// I never pass here
return Parse.Promise.as();
}
});
}
});
});
return promise;
},
error: function (error) {
// I never pass here
return Parse.Promise.error(error);
}
}).then(function (nil) {
// I never pass here
return Parse.Promise.as("DELETEOLDITEMS: Job finished");
}, function(error) {
// I never pass here
return Parse.Promise.error(error);
});
}
(I have tested to replace every // I never pass here with console.log(), without any result)
I tried many different things but I believe this should work! Or at least return errors!
Anyone know what I am doing wrong? Thanks in advance!
EDIT:
Even weirder, if I modify performMaintenanceTasks to skip query.count():
function performMaintenanceTasks()
{
return deleteOldItems(1);
}
the query.find() in deleteOldItems() is correctly executed this time!
What does that mean? Am I not allowed to nest queries on the same class?
I'm not certain if this pertains to you, but I know from my a personal experience that the Parse log can seem a little unintuitive. The Parse log only spits out 10 lines by default, so ensure you're specifying the log length every time you check.
parse log -n 1000
...is what I tend to do every time. This just makes debugging easier.
I use sails.js to update stock data from difference variable. When I do, console.log(product.stock), the value is 4. But it seems product.save() function below is not executed because attribute stock still not changed to 4. I guess the problem is at promise. Anybody know how to fix this?
exports.updateProductStock = function (details) {
details.forEach(function( detail ) {
var findPrevDetailRule = {
invoice: detail.invoice,
product: detail.product.id
};
var createPrevDetailRule = {
invoice: detail.invoice,
product: detail.product.id,
quantity: detail.quantity
};
var requirementBeforeUpdateStock = [
PreviousDetail.findOne(findPrevDetailRule).sort('createdAt desc').then(),
PreviousDetail.create(createPrevDetailRule).then()
];
Q.all(requirementBeforeUpdateStock)
.spread(function( prevDetail, newPrevDetail ) {
var difference = detail.quantity - prevDetail.quantity;
return {
prevDetail: prevDetail,
difference: difference
};
})
.then(function( results ) {
Product.findOne(results.prevDetail.product).then(function(product) {
product.stock += results.difference;
// maybe this below is not execute
product.save();
console.log(product.stock);
});
})
});
Note: I use sails 0.10-rc8. Have tested with sails-mysql, sails-mongo, sails-postgres, but still same.
.save() accepts a callback with which you can report success/failure.
For example, you can repost back to the client (from the sails documentation) :
product.save(function (err) {
if (err) return res.send(err, 500);
res.json(product);
});
Depending on scope, you may need to pass res into your function
Alternatively, monitor success/failure in the console, eg ,
product.save(function (err) {
if (err) console.log('save error: ' + err);//assuming err to be a string
console.log('save success');
});
Should at least give you a clue as to what's going wrong.