I have a controller making two $resource calls against two REST services where the result of the first one is used as input by the second one.
Here the code:
if (requestLock == false) {
$scope.T_01_04_sharedData.tempRequestForT_01_04 = insertNewRequest("aggr_1", $rootScope.globals.currentUser.username, "", "temp", "2016-07-30 00:00:00");
requestLock = true;
}
if (action == 'add') {
updateSelectedForRequest(prosumer, 'selected', $rootScope.globals.currentUser.username, $scope.T_01_04_sharedData.tempRequestForT_01_04);
} else {
updateSelectedForRequest(prosumer, 'non-selected', $rootScope.globals.currentUser.username, $scope.T_01_04_sharedData.tempRequestForT_01_04);
}
Function updateSelectedForRequest
function updateSelectedForRequest(username, status, businessUser, request) {
WidgetService.T_01_04_updateSelectedForRequest.query({
businessUser_id: businessUser,
request_id: request,
username: username,
status: status
}, function (result) {
// response handler
});
}
Function insertNewRequest
function insertNewRequest(bu_id_target, requester, description, status, validUntil) {
return WidgetService.T_01_04_insertNewRequest.query({
bu_id_target: bu_id_target,
requester: requester,
description: description,
status: status,
validUntil: validUntil
}, function (result) {
$scope.T_01_04_sharedData.tempRequestForT_01_04 = result.request_id;
return result;
});
}
The error is that the first call is not resolved sequentially so the second one has no input.
Is there the possibility to run these two calls sequentially in order to wait for the second call the input from the first one?
Thanks a lot.
I'm not familiar with ngresource, but you can try something like this.
if (requestLock == false) {
insertNewRequest("aggr_1", $rootScope.globals.currentUser.username, "", "temp", "2016-07-30 00:00:00")
.then(function(result){
$scope.T_01_04_sharedData.tempRequestForT_01_04 = result;
if (action == 'add') {
updateSelectedForRequest(prosumer, 'selected', $rootScope.globals.currentUser.username, $scope.T_01_04_sharedData.tempRequestForT_01_04);
} else {
updateSelectedForRequest(prosumer, 'non-selected', $rootScope.globals.currentUser.username, $scope.T_01_04_sharedData.tempRequestForT_01_04);
}
}, function(error){/* manage error here */});
requestLock = true;
}
function insertNewRequest(bu_id_target, requester, description, status, validUntil) {
return new Promise(function(resolve, reject){
PromiseWidgetService.T_01_04_insertNewRequest.query({
bu_id_target: bu_id_target,
requester: requester,
description: description,
status: status,
validUntil: validUntil
}, function (result) {
$scope.T_01_04_sharedData.tempRequestForT_01_04 = result.request_id;
resolve(result);
});
})
}
more information on promise here : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
Related
2nd UPDATE
We are implementing a BulkEdit functionality which sends async CRUD requests to a Backend.
So what I require here is a dynamically created set of nested promises.
In an abstract version the data array could look like:
var objArr = [
{
name: 'A',
subs: [
{
id: 1,
_action: 'create'
},
{
id: 2,
_action: 'create'
},
{
id: 3,
_action: 'delete'
}
]
},
{
name: 'B',
subs: [
{
id: 4,
_action: 'create'
},
{
id: 5,
_action: 'put'
}
]
},
{
name: 'C',
subs: []
}
];
I try to illustrate how the requests should be sent for this data following the order given by '_action'.
Get some transaction ID (see below)
As soon as transaction ID is there start to send requests for every Object in the Array given the following rules:
Per Object send all 'delete' requests at once if there are any.
After that or if there weren't any 'delete' requests send all 'put'
requests if there are any.
After 'delete' and/or 'put' send all 'create' requests if there are any.
As soon as all requests for an Object are done, do something per Object.
As soon as all Objects are done, close the Transaction.
How is it possible to create this dynamic nested/non-nested promise chain?
UPDATED CODE contains now Promise Creation
When calling the function below, first a TransactionService gets a transaction id which is required to be sent with each request. When everything is successful, the Transaction will be closed.
My current issue is that promises are not resolved in the correct order (while the OPTIONS preflight requests seem to be) and that this example creates Promises even if they are not required (e.g. for Object 'C' in the example above).
function startIt(objArr) {
TransactionService.getTransaction().then(function (transaction) {
var promiseArray = MyService.submit(objArr, transaction.id);
$q.all(promiseArray).then(function () {
Transactions.closeTransaction(transaction.id, function () {}).then(function () {
});
};
});
}
This is the function for 'submit':
function submit(objArr, transactionId) {
var promises = objArr.map(function (obj) {
return submitWithTransId(transactionId, obj)
.then(function (response) {
// Object done
});
});
return promises;
}
And this function is actually creating the Promises:
function submitWithTransId(transactionId, obj) {
var promisesDelete = [];
var promisesUpdate = [];
var promisesCreate = [];
angular.forEach(obj['subs'], function (sub) {
switch (sub._action) {
case 'delete':
promisesDelete.push(createPromise(sub, bulktransactionId));
break;
case 'put':
promisesUpdate.push(createPromise(sub, bulktransactionId));
break;
case 'create':
promisesCreate.push(createPromise(sub, bulktransactionId));
break;
}
});
var chainedPromises = $q.all(promisesDelete).then(function (deleteResponse) {
return $q.all(promisesUpdate).then(function (updateResponse) {
return $q.all(promisesCreate).then(function (createResponse) {
});
});
});
return chainedPromises;
And this is my createPromise function:
/** only simplified handling create case **/
function createPromise(sub, bulktransactionId) {
var queryParams = {};
if (bulktransactionId !== undefined && bulktransactionId !== null) {
queryParams.transaction_id = bulktransactionId;
}
var promise = MyResourceService.create(queryParams, sub).$promise;
promise.then(function (newSub) {
// do something with the new/updated/deleted sub, especially update view model
});
return promise;
}
/** MyResourceService **/
return $resource(ENV.apiEndpoint + 'api/v1/subs/:_id',
{
_id: '#id'
}, {
create: {
method: 'POST'
}
}
);
You can have a look at the following solution. The objective is to provide you some sort of structure. Please see, you will have to modify to your use.
var objArr = [{
name: 'A',
subs: [{
id: 1,
_action: 'create'
},
{
id: 2,
_action: 'create'
},
{
id: 3,
_action: 'delete'
}
]
},
{
name: 'B',
subs: [{
id: 4,
_action: 'create'
},
{
id: 5,
_action: 'put'
}
]
},
{
name: 'C',
subs: []
}
];
var promises = objArr.map(function(obj) {
return firstLevelPromise(obj)
.then(function(response) {
console.log(response); // promise for each object
return response;
});
});
$q.all(promises)
.then(function(response) {
console.log(response); // completion - close transaction
});
function firstLevelPromise(obj) {
var deletePromises = [];
var putPromies = [];
var insertPromies = [];
obj.subs.forEach(function(sub) { // Preparing promises array for delete, put and insert
if (sub._action === "delete") {
deletePromises.push(deletePromise(sub));
} else if (sub._action === "put") {
putPromies.push(putPromise(sub));
} else {
insertPromies.push(insertPromise(sub));
}
});
return $q.all(deletePromises) // executing delete promises
.then(function(deleteResponse) {
console.log("deleteExecuted: " + obj.name);
return $q.all(putPromies) // on completion of delete, execute put promies
.then(function(putResponse) {
console.log("putExecuted: " + obj.name);
return $q.all(insertPromies) // on completion of put, execute insert promises
.then(function(insertResponse) {
console.log("insertExecuted: " + obj.name);
return "object promise completed: " + obj.name; // on completion, return
});
});
});
}
function deletePromise(task) {
return $q.resolve(task); // write your delete code here
}
function putPromise(task) {
return $q.resolve(task); // write your put code here
}
function insertPromise(task) {
return $q.resolve(task); // write your insert code here
}
Please note, the above code will do the following
Prepare a collection of promises where there is one promise for each object in objectArray
Each promise will have promises chain i.e. delete promises, followed by put promises and finally followed by insert promises i.e. it ensures that for each object perform the delete tasks, then on completion perform the put tasks and then on its completion perform the insert tasks.
Here is a plunker and documentation for $q
UPDATE
The problem is with the createPromise function only. Update your code to following. The problem was that you are calling the then before returning, hence, it is likely that the you are returning a resolved promise like $q.resolve(). In this case, it is possible that a create request gets resolved before delete or put and a put calls gets resolved before delete. Hence, you should return the promise from here and perform the post action things in the $q.all block.
function createPromise(sub, bulktransactionId) {
var queryParams = {};
if (bulktransactionId !== undefined && bulktransactionId !== null) {
queryParams.transaction_id = bulktransactionId;
}
return MyResourceService.create(queryParams, sub).$promise;
}
Try this - the trick is to accumulate the promise (that's what I'm using the reduce for. Hope it helps.
// reversed the order => delete actions first; otherwise you may have to do extra logic may be needed
var objArr = [
{ name: 'A',
subs: [
{ id: null,
_action: 'delete'
},
{ id: 2,
_action: 'create']
}
},
{ name: 'B',
subs: [
{ id: 3.
_action: 'create'
}
]
];
Promise.all(objArr.map(obj => {
return obj.subs.reduce((accumulatedPromisedSub, sub) => {
return accumulatedPromisedSub.then(_ => yourRequestCallHere(sub) )
},
Promise.resolve(true) // you could also do your delete here if you like
)
}))
// OR going sort order agnostic:
Promise.all(objArr.map(obj => {
const deleteAction = obj.subs.find(sub => sub._action === 'delete');
return obj.subs.reduce((accumulatedPromisedSub, sub) => {
if (sub._action === 'delete') return accumulatedPromisedSub;
return accumulatedPromisedSub.then(_ => yourRequestCallHere(sub) )
},
deleteAction ? yourRequestCall(deleteAction) : Promise.resolve(true)
)
}))
I am so happy and thankful, I found it.
To my understanding I had two main issues in my code.
As $resource is not providing a (please forgive me, pros!) real $promise even when I use something like .get().$promise I had to
use bind to map the createPromise function to my arrays
do the mapping only directly before returning the $q.all promise.
In any other case $resource seemed to immediately fill its promise with an empty object and $q.all did not work anymore as promises looked like they where resolved.
Special thanks to #nikhil who supported me in finding this ugly complex thing and who earned the bounty.
This is the working snippet:
function submitWithTransId(transactionId, obj) {
var arrayDelete = [];
var arrayUpdate = [];
var arrayCreate = [];
angular.forEach(obj['subs'], function (sub) {
switch (sub._action) {
case 'delete':
arrayDelete.push(sub);
break;
case 'update':
arrayUpdate.push(sub);
break;
case 'create':
arrayCreate.push(sub);
break;
}
});
var promisesDelete = flattenArray(arrayDelete.map(createPromise.bind(undefined, obj, transactionId)));
return $q.all(promisesDelete).then(function (deleteResponse) {
console.log('Delete Promises ' + obj.name + ' resolved');
var promisesUpdate = flattenArray(arrayUpdate.map(createPromise.bind(undefined, obj, transactionId)));
return $q.all(promisesUpdate).then(function (updateResponse) {
var promisesCreate = flattenArray(arrayCreate.map(createPromise.bind(undefined, obj, transactionId)));
console.log('Update Promises ' + obj.name + ' resolved');
return $q.all(promisesCreate).then(function (createResponse) {
console.log('Create Promises ' + obj.name + ' resolved');
});
});
}).catch(function (error) {
console.log('Catched an error: ');
console.log(error);
});
});
I want to add "where" query in Sequalize based on flag . Let me know how to get this.
For example if the flag is true then I want to add this "where" class otherwise I don't want to add the "where" clause.
models.Users.findAll({
where: {
email: req.query.email
}
}).then(function (list) {
res.send(list);
}).catch(function (error) {
res.status(500).json(error);
});
models.Users.findAll({
}).then(function (list) {
res.send(list);
}).catch(function (error) {
res.status(500).json(error);
});
You can create JSON first which you are providing in where clause and then pass it in where clause.. something like
var jsonObj = null ;
if (req.query.email){
jsonObj = {"email": req.query.email};
} else {
jsonObj = {"email": {$ne:null}}; //otherwise it will return all rows, if email is not null in your database
}
models.Users.findAll({
where: jsonObj
}).then(function (list) {
res.send(list);
}).catch(function (error) {
res.status(500).json(error);
});
Hope this will help
You can start with an empty query object, and add a where property to it when appropriate:
let query = {};
if (someCondition) {
query.where = { email: req.query.email };
}
models.Users.findAll(query).then(...);
You can use simple one-line if statement inside the findAll method call. Let's assume that variable is your boolean flag
models.Users.findAll(variable ? { where: { email: req.query.email } } : {}).then(list => {
res.send(list);
}).catch(error => {
res.status(500).json(error);
});
Or you can assign the options object to a variable and use the same if statement, but outside the findAll call
let options = variable ? { where: { email: req.query.email } } : {};
models.Users.findAll(options).then(list => {
// ...
});
I want to compare the data which I got from Mongo to javascript array. I am using lodash to compare. But it always return incorrect result.
var editUser = function(userData, getOutFunction) {
var status = CONSTANTS.NG;
checkExistUser(userData._id).then(function(user) {
if (user !== null) {
var userGroup = JSON.stringify(user.group);
user.group = user.group.map((groupId) => {
return groupId.toString();
});
var removedGroups = _.difference(userGroup, userData.group);
var addedGroups = _.difference(userData.group, userGroup);
console.log('Removed Groups: ', removedGroups);
console.log('Added Groups: ', addedGroups);
} else {
status = CONSTANTS.NG;
logger.debug(DEBUG_CLASS_NAME, "Cannot find object");
if (typeof(getOutFunction) !== 'undefined') {
getOutFunction(status, null);
} else {
NO_CALLBACK();
}
}
}).catch(function() {
console.log('Promise is error');
});
var checkExistUser = function(userId) {
return new Promise(function(resolve, reject) {
UserDAO.findById(userId, function(err, user) {
if (err) {
logger.debug(DEBUG_CLASS_NAME, {
name: err.name,
code: err.code,
message: err.message,
method: "checkExist"
});
resolve(null);
} else {
resolve(user);
}
});
});
}
For example:When I try to input value for lodash difference function
var user.group = ["58b8da67d585113517fed34e","58b8da6ed585113517fed34f"];
var userData.group = [ '58b8da67d585113517fed34e' ];
I want lodash difference return below result:
Removed Groups: ['58b8da6ed585113517fed34f']
Added Groups: []
However, the function gave me the result like:
Removed Groups: []
Added Groups: [ '58b8da67d585113517fed34e' ]
Can anyone help me in this case?
I will do appreciate it.
I have had this issue as well, the result from mongodb is an ObjectId type so you can compare the someObjectId.toString() value with your array of strings, or you could use
someObjectId.equals(stringOrObjectIdValue)
However, if you want to keep using lodash functions you will either have to force both arrays to strings or to ObjectIds before passing them into the function.
I need to return a rejected promise from a js function. I am using angular $q as you can see. But it doesn't work.
In function getDBfileXHR, when the promise getDBfileXHRdeferred is rejected using getDBfileXHRdeferred.reject() I would to pass into the the error case of the function getDBfileXHR and run fallbackToLocalDBfileOrLocalStorageDB(). But it doesn't work.
Is there a syntax error ?
I am a bit new to promises.
Thanks
this.get = function () {
var debugOptionUseLocalDB = 0,
prodata = [],
serverAttempts = 0;
if (debugOptionUseLocalDB) {
return fallbackToLocalDBfileOrLocalStorageDB();
}
if (connectionStatus.f() === 'online') {
console.log("Fetching DB from the server:");
return getDBfileXHR(dbUrl(), serverAttempts)
.then(function () { // success
console.log('-basic XHR request succeeded.');
return dbReadyDeferred.promise;
}, function () { // error
console.log("-basic XHR request failed, falling back to local DB file or localStorage DB...");
return fallbackToLocalDBfileOrLocalStorageDB();
});
}
}
function getDBfileXHR(url, serverAttempts) {
var getDBfileXHRdeferred = $q.defer(),
request = new XMLHttpRequest();
if (typeof serverAttempts !== "undefined") serverAttempts++;
request.open("GET", url, true); //3rd parameter is sync/async
request.timeout = 2000;
request.onreadystatechange = function () { // Call a function when the state changes.
if ((request.readyState === 4) && (request.status === 200 || request.status === 0)) {
console.log('-we get response '+request.status+' from XHR in getDBfileXHR');
var jsonText = request.responseText.replace("callback(", "").replace(");", "");
if (jsonText === '') {
console.error('-error : request.status = ' + request.status + ', but jsonText is empty for url=' + url);
if (serverAttempts <= 2){
sendErrorEmail("BL: jsonText is empty, trying to reach server another time", 11);
getDBfileXHR(url, serverAttempts);
return;
} else {
sendErrorEmail("BL: jsonText is empty and attempted to reach server more than twice", 14);
var alertPopup = $ionicPopup.alert({
title: 'Error '+"11, jsonText is empty",
template: "Sorry for the inconvenience, a warning email has been sent to the developpers, the app is going to restart.",
buttons: [{
text:'OK',
type: 'button-light'
}]
});
getDBfileXHRdeferred.reject();
}
} else {
}
} else {
console.error('-error, onreadystatechange gives : request.status = ' + request.status);
getDBfileXHRdeferred.reject();
}
};
if (url === "proDB.jsonp") {
console.log("-Asking local proDB.json...");
} else {
console.log("-Sending XMLHttpRequest...");
}
request.send();
return getDBfileXHRdeferred.promise;
}
EDIT:
I rewrote my function using this approach. It seems better and cleaner like this. But now can you help me handle the multiple attempds ?
function getDBfileXHR(url, serverAttempts) {
return new Promise(function (resolve, reject) {
var request = new XMLHttpRequest();
request.open("GET", url, true); request.timeout = 2000;
var rejectdum;
if (url === "proDB.jsonp") {
console.log("-Asking local proDB.json...");
} else {
console.log("-Sending XMLHttpRequest...");
}
request.onload = function () {
if ( (request.readyState === 4) && (request.status === 200 || request.status === 0) ) {
console.log('-we get response '+request.status+' from XHR in getDBfileXHR');
var jsonText = request.responseText.replace("callback(", "").replace(");", "");
if (jsonText === '') {
console.error('-error : request.status = ' + request.status + ', but jsonText is empty for url=' + url);
sendErrorEmail("BL: jsonText is empty, trying to reach server another time", 11);
sendErrorEmail("BL: jsonText is empty and attempted to reach server more than twice", 14);
var alertPopup = $ionicPopup.alert({
title: 'Error '+"11, jsonText is empty",
template: "The surfboard database could not be updated, you won't see the new models in the list, sorry for the inconvenience.",
buttons: [{
text:'OK',
type: 'button-light'
}]
});
console.log('oui on passe rejectdum')
rejectdum = 1;
reject({
status: this.status,
statusText: request.statusText
});
} else {
var parsedJson;
try {
parsedJson = JSON.parse(jsonText);
} catch (e) {
console.warn("Problem when trying to JSON.parse(jsonText) : ");
console.warn(e);
console.warn("parsedJson :");
console.warn(parsedJson);
}
if (parsedJson) {
var prodata = jsonToVarProdata(parsedJson);
console.log('-writing new prodata to localStorage');
console.log('last line of prodata:' + prodata[prodata-1]);
storageService.persist('prodata', prodata);
storageService.store('gotANewDB', 1);
}
resolve(request.response);
dbReadyDeferred.resolve();
}
}
};
request.onerror = function () {
reject({
status: this.status,
statusText: request.statusText
});
};
request.send();
});
}
Is it a clean way to do this to do several attempts :
return getDBfileXHR(dbUrl(), serverAttempts)
.then(function () { // success
console.log('-basic XHR request succeeded.');
return dbReadyDeferred.promise;
})
.catch(function (){
if (typeof serverAttempts !== "undefined") serverAttempts++;
console.log('on passe dans le catch, serverAttempts = ', serverAttempts)
if (serverAttempts < 2) {
return getDBfileXHR(dbUrl(), serverAttempts)
.then(function () { // success
console.log('-basic XHR request succeeded.');
return dbReadyDeferred.promise;
})
.catch(function (){
console.log("-basic XHR request failed, falling back to local DB file or localStorage DB...");
return fallbackToLocalDBfileOrLocalStorageDB();
})
} else {
console.log("-basic XHR request failed, falling back to local DB file or localStorage DB...");
return fallbackToLocalDBfileOrLocalStorageDB();
}
})
if you remove the code to retry (twice?) on failure your code would possibly work (haven't looked into that) -
the issue is, the only promise your calling code gets is that of the first attempt. If the first attempt fails, that promise is never resolved or rejected
You need to resolve the promise with the promise returned by getDBfileXHR(url, serverAttempts); - so, something like
if (serverAttempts <= 2){
sendErrorEmail("BL: jsonText is empty, trying to reach server another time", 11);
getDBfileXHRdeferred.resolve(getDBfileXHR(url, serverAttempts));
return;
} else {
Because if promise(1) resolves to a rejected promise(2), the result is that promise(1) rejects with the rejection value of promise(2)
This is how native Promises, and many many Promise/A+ compliant libraries work,
so this should be the case with $.defer if it follows the Promise/A+ spec
I am new in nodejs and I want to make a json array by comparing id inside a loop but the MongoDB function does not wait for the loop to complete, so data is not coming out properly. It displays the data before the loop ends, below is the code:
router.get('/getallcountrydataup',function(req, res) {
Country
.where('isDeleted').equals(false)
.exec(function(err,cData){
if (!cData) {
res.json({'status':0,'message': 'No data found','data':[]});
} else{
async.waterfall([
function (done) {
var countryalldata = [];
for (var i = 0; i < cData.length; i++) {
var country_s = cData[i];
State.where('s_c_id').equals(country_s._id)
.exec(function(err, statedata){
country_s.statecount = statedata.length;
//console.log(country_s._id);
console.log(country_s.statecount);
});
countryalldata.push(country_s);
}
done(err, countryalldata);
// console.log(countryalldata);
},
function (countryalldata, done) {
console.log(countryalldata);
res.json({
'status': 1,
'message': 'Data found',
'data': countryalldata
});
}
]);
}
});
});
Here is output of the countryalldata variable printed before the loop will complete. I want to print its output after loop execution is complete.
State.where is asynchronous so it should be synchronized. For example with async.map
router.get('/getallcountrydataup',function(req, res) {
Country
.where('isDeleted').equals(false)
.exec(function(err,cData){
if (!cData) {
res.json({'status':0,'message': 'No data found','data':[]});
} else {
async.waterfall([
function (done) {
async.map(cData, function (country_s, done2) {
// Convert country_s into plain object. It's not a mongoose
// model anymore
country_s = country_s.toObject();
State.where('s_c_id')
.equals(country_s._id)
.exec(function(err,statedata){
if (err) {
done2(err);
return;
}
country_s.statecount = statedata.length;
console.log(country_s.statecount);
done2(null, country_s)
});
}, function(err, countryalldata) {
if (err) {
done(err);
}
else {
done(null, countryalldata)
}
});
},
function (countryalldata, done) {
console.log(countryalldata);
res.json({'status':1,'message': 'Data found','data':countryalldata});
}
]);
}
});
});