I am using the when library with Node js. I create a deffered object, place the resolve inside an encapsulated Mongoose findOne() function, and return the promise outside. But it seems my promise is always returned before the data is retrieved.
User.prototype.getProfile = function(criteria) {
var deferred = when.defer();
var options = {
criteria: criteria,
select: 'name id email'
};
this.User.load(options, function(err, data) {
if (data) {
this.name = data.name;
this.email = data.email;
this.id = data.id;
} else {
return false;
}
console.log(data);
deferred.resolve();
});
console.log('returning promise');
return deferred.promise;
};
Caller
User.getProfile(req.query).then(
function success(data) {
res.send('Hello ' + User.name);// Hello ''
}
);
Outputs 'returning promise' before the data
Yes, promise will be returned to the caller instead of the data and that is how we can take advantage of the asynchronous functions. This is the common sequence of actions in handling async calls,
Make an async call.
Return a Promise to the caller.
At this point, caller doesn't have to wait for the result. It can simply define a then function, which knows what to do when the data is ready and move on to the next task.
Later point of time, resolve (or reject, if failed) the promise when you get the result from the async call.
Execute the then function on the Promise object, with the result from the async call.
So, your code will have to be modified a little bit, like this
User.prototype.getProfile = function(criteria) {
var deferred = when.defer();
var options = {
criteria: criteria,
select: 'name id email'
};
this.User.load(options, function(err, data) {
if (err) {
// Reject, if there is an error
deferred.reject(err);
} else {
// Resolve it with actual data
deferred.resolve(data);
}
});
return deferred.promise;
};
Then your caller will do something like this
userObject.getProfile()
.then(function(profileObject) {
console.log(profileObject);
// Do something with the retrieved `profileObject`
})
.catch(function(err) {
console.err("Failed to get Profile", err);
});
// Do something else here, as you don't have to wait for the data
Here, caller just calls getProfile and attaches a function which says what to do with the returned data and moves on.
Edit If you want the same object to be updated, then you can simply use similar code, but you need to preserve this in some other variable, because the binding of this happens at runtime.
User.prototype.getProfile = function(criteria) {
var deferred = when.defer();
var options = {
criteria: criteria,
select: 'name id email'
};
var self = this;
this.User.load(options, function(err, data) {
if (err) {
// Reject, if there is an error
deferred.reject(err);
} else {
self.name = data.name;
self.email = data.email;
self.id = data.id;
}
deferred.resolve(data);
});
return deferred.promise;
};
That's how promises work.
Since you have an async task that takes some time, and JavaScript is a single threaded language, you don't want to block your code and wait for that async operation to complete itself - otherwise nobody would use JavaScript!!
So what do you do? You create a promise and continue your code.
You add callbacks to that promise and when the promise is resolved your callbacks are invoked.
I didn't use the when library but what you want to do is something like this:
User.prototype.getProfile = function(criteria){
var deferred = when.defer();
var options = {
criteria : criteria,
select : 'name id email'
};
this.User.load(options, function(err, data) {
if (data) {
this.name = data.name;
this.email = data.email;
this.id = data.id;
console.log(data);
// the callback will invoke after the deferred object is resolved.
deferred.promise.then(function(o){ console.log('resolved!!!'); });
deferred.resolve(data);
}else{
deferred.reject('something bad occured');
return false;
}
});
return deferred.promise;
};
Related
I am trying to wrap my post/get/put/delete calls so that any time they are called, if they fail they will check for expired token, and try again if that is the reason for failure, otherwise just resolve the response/error. Trying to avoid duplicating code four times, but I'm unsure how to resolve from a non-anonymous callback.
factory.post = function (url, data, config) {
var deferred = $q.defer();
$http.post(url, data, config).then(factory.success, factory.fail);
return deferred.promise;
}
factory.success = function (rsp) {
if (rsp) {
//how to resolve parent's promise from from here
}
}
Alternative is to duplicate this 4 times:
.then(function (rsp) {
factory.success(rsp, deferred);
}, function (err) {
factory.fail(err, deferred);
});
One solution might be using bind function.
function sum(a){
return a + this.b;
}
function callFn(cb){
return cb(1);
}
function wrapper(b){
var extra = {b: b};
return callFn(sum.bind(extra));
}
console.log(wrapper(5));
console.log(wrapper(-5));
console.log(wrapper(50));
For your solution check bellow example
factory.post = function (url, data, config) {
var deferred = $q.defer();
$http.post(url, data, config).then(factory.success.bind({deferred: deferred}), factory.fail.bind({deferred: deferred}));
return deferred.promise;
}
factory.success = function (rsp) {
if (rsp) {
this.deferred.resolve(rsp);
//how to resolve parent's promise from from here
}else {
//retry or reject here
}
}
From what I understand, you just want to resolve the deferred object on success and retry on error in case of expired token. Also you probably want to keep a count of number of retries. If so,
Edit - Seems I misunderstood the question. The answer suggested by Atiq should work, or if you are using any functional JS libraries like underscore or Ramdajs, you could use curry function. Using curry function, you can pass some parameters to the function and the function will get executed only after all the parameters are passed. I have modified the code snippet to use curry function from underscorejs.
factory.post = function (url, data, config) {
var deferred = $q.defer();
$http.post(url, data,
config).then(_.curry(factory.success(deferred)),
_.curry(factory.fail(deferred));
return deferred.promise;
}
factory.success = function (deferred, rsp) {
if (rsp) {
//handle resp
deferred.resolve(rsp);
}
}
factory.fail = function(deferred, err){
//handle retry
deferred.reject(err);
}
I want to write the unit test for the factory which have lot chain of promises. Below is my code snippet:
angular.module('myServices',[])
.factory( "myService",
['$q','someOtherService1', 'someOtherService2', 'someOtherService3', 'someOtherService4',
function($q, someOtherService1, someOtherService2, someOtherService3, someOtherService4) {
method1{
method2().then(
function(){ someOtherService3.method3();},
function(error){/*log error;*/}
);
return true;
};
var method2 = function(){
var defer = $q.defer();
var chainPromise = null;
angular.forEach(myObject,function(value, key){
if(chainPromise){
chainPromise = chainPromise.then(
function(){return method4(key, value.data);},
function(error){/*log error*/});
}else{
chainPromise = method4(key, value.data);
}
});
chainPromise.then(
function(){defer.resolve();},
function(error){defer.reject(error);}
);
return defer.promise;
};
function method4(arg1, arg2){
var defer = $q.defer();
someOtherService4.method5(
function(data) {defer.resolve();},
function(error) {defer.reject(error);},
[arg1,arg2]
);
return defer.promise;
};
var method6 = function(){
method1();
};
return{
method6:method6,
method4:method4
};
}]);
To test it, I have created spy object for all the services, but mentioning the problematic one
beforeEach( function() {
someOtherService4Spy = jasmine.createSpyObj('someOtherService4', ['method4']);
someOtherService4Spy.method4.andCallFake(
function(successCallback, errorCallback, data) {
// var deferred = $q.defer();
var error = function (errorCallback) { return error;}
var success = function (successCallback) {
deferred.resolve();
return success;
}
return { success: success, error: error};
}
);
module(function($provide) {
$provide.value('someOtherService4', someOtherService4);
});
inject( function(_myService_, $injector, _$rootScope_,_$q_){
myService = _myService_;
$q = _$q_;
$rootScope = _$rootScope_;
deferred = _$q_.defer();
});
});
it("test method6", function() {
myService.method6();
var expected = expected;
$rootScope.$digest();
expect(someOtherService3.method3.mostRecentCall.args[0]).toEqualXml(expected);
expect(someOtherService4Spy.method4).toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function), [arg,arg]);
expect(someOtherService4Spy.method4).toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function), [arg,arg]);
});
It is showing error on
expect(someOtherService3.method3.mostRecentCall.args[0]).toEqualXml(expected);
After debugging I found that it is not waiting for any promise to resolve, so method 1 return true, without even executing method3. I even tried with
someOtherService4Spy.method4.andReturn(function(){return deferred.promise;});
But result remain same.
My question is do I need to resolve multiple times ie for each promise. How can I wait till all the promises are executed.
method1 does not return the promise so how would you know the asynchrounous functions it calls are finished. Instead you should return:
return method2().then(
method6 calls asynchronous functions but again does not return a promise (it returns undefined) so how do you know it is finished? You should return:
return method1();
In a test you should mock $q and have it resolve or reject to a value but I can't think of a reason why you would have a asynchronous function that doesn't return anything since you won't know if it failed and when it's done.
Method 2 could be written in a more stable way because it would currently crash if the magically appearing myObject is empty (either {} or []
var method2 = function(){
var defer = $q.defer();
var keys = Object.keys(myObject);
return keys.reduce(
function(acc,item,index){
return acc.then(
function(){return method4(keys[index],myObject[key].data);},
function(err){console.log("error calling method4:",err,key,myObject[key]);}
)
}
,$q.defer().resolve()
)
};
And try not to have magically appearing variables in your function, this could be a global variable but your code does not show where it comes from and I doubt there is a need for this to be scoped outside your function(s) instead of passed to the function(s).
You can learn more about promises here you should understand why a function returns a promise (functions not block) and how the handlers are put on the queue. This would save you a lot of trouble in the future.
I did below modification to get it working. I was missing the handling of request to method5 due to which it was in hang state. Once I handled all the request to method 5 and provided successCallback (alongwith call to digest()), it started working.
someOtherService4Spy.responseArray = {};
someOtherService4Spy.requests = [];
someOtherService4Spy.Method4.andCallFake( function(successCallback, errorCallback, data){
var request = {data:data, successCallback: successCallback, errorCallback: errorCallback};
someOtherService4Spy.requests.push(request);
var error = function(errorCallback) {
request.errorCallback = errorCallback;
}
var success = function(successCallback) {
request.successCallback = successCallback;
return {error: error};
}
return { success: success, error: error};
});
someOtherService4Spy.flush = function() {
while(someOtherService4Spy.requests.length > 0) {
var cachedRequests = someOtherService4Spy.requests;
someOtherService4Spy.requests = [];
cachedRequests.forEach(function (request) {
if (someOtherService4Spy.responseArray[request.data[1]]) {
request.successCallback(someOtherService4Spy.responseArray[request.data[1]]);
} else {
request.errorCallback(undefined);
}
$rootScope.$digest();
});
}
}
Then I modified my test as :
it("test method6", function() {
myService.method6();
var expected = expected;
var dataDict = {data1:"data1", data2:"data2"};
for (var data in dataDict) {
if (dataDict.hasOwnProperty(data)) {
someOtherService4Spy.responseArray[dataDict[data]] = dataDict[data];
}
}
someOtherService4Spy.flush();
expect(someOtherService3.method3.mostRecentCall.args[0]).toEqualXml(expected);
expect(someOtherService4Spy.method4).toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function), [arg,arg]);
});
This worked as per my expectation. I was thinking that issue due to chain of promises but when I handled the method5 callback method, it got resolved. I got the idea of flushing of requests as similar thing I was doing for http calls.
Let's say I have some code that looks like this:
var doSomething = function(parameter){
//send some data to the other function
return when.promise(function(resolveCallback, rejectCallback) {
var other = doAnotherThing(parameter);
//how do I check and make sure that other has resolved
//go out and get more information after the above resolves and display
});
};
var doAnotherThing = function(paramers){
return when.promise(function(resolveCallback, rejectCallback) {
//go to a url and grab some data, then resolve it
var s = "some data I got from the url";
resolveCallback({
data: s
});
});
};
How do I ensure that var other has completely resolved before finishing and resolving the first doSomething() function? I'm still wrapping my head around Nodes Async characteristic
I really didn't know how else to explain this, so I hope this makes sense! Any help is greatly appreciated
EDIT: In this example, I am deleting things from an external resource, then when that is done, going out the external resource and grabbing a fresh list of the items.
UPDATED CODE
var doSomething = function(parameter){
//send some data to the other function
doAnotherThing(parameter).then(function(){
//now we can go out and retrieve the information
});
};
var doAnotherThing = function(paramers){
return when.promise(function(resolveCallback, rejectCallback) {
//go to a url and grab some data, then resolve it
var s = "some data I got from the url";
resolveCallback({
data: s
});
});
};
The return of doAnotherThing appears to be a promise. You can simply chain a then and put your callback to utilize other. then also already returns a promise. You can return that instead.
// Do stuff
function doSomething(){
return doAnotherThing(parameter).then(function(other){
// Do more stuff
return other
});
}
// Usage
doSomething().then(function(other){
// other
});
Below is how to accomplish what you're trying to do with bluebird.
You can use Promise.resolve() and Promise.reject() within any function to return data in a Promise that can be used directly in your promise chain. Essentially, by returning with these methods wrapping your result data, you can make any function usable within a Promise chain.
var Promise = require('bluebird');
var doSomething = function(parameter) {
// Call our Promise returning function
return doAnotherThing()
.then(function(value) {
// Handle value returned by a successful doAnotherThing call
})
.catch(function(err) {
// if doAnotherThing() had a Promise.reject() in it
// then you would handle whatever is returned by it here
});
}
function doAnotherThing(parameter) {
var s = 'some data I got from the url';
// Return s wrapped in a Promise
return Promise.resolve(s);
}
You can use the async module and its waterfall method to chain the functions together:
var async = require('async');
async.waterfall([
function(parameter, callback) {
doSomething(parameter, function(err, other) {
if (err) throw err;
callback(null, other); // callback with null error and `other` object
});
},
function(other, callback) { // pass `other` into next function in chain
doAnotherThing(other, function(err, result) {
if (err) throw err;
callback(null, result);
})
}
], function(err, result) {
if (err) return next(err);
res.send(result); // send the result when the chain completes
});
Makes it a little easier to wrap your head around the series of promises, in my opinion. See the documentation for explanation.
Background
I have a hierarchy of entities that looks like (Manufacturer -> Vehicle Type -> Vehicle Model -> Vehicle Submodel). Each manufacturer has multiple vehicle types, each vehicle type has multiple models, and each model has multiple submodels.
I need to retrieve a list of vehicle models given a manufacturer and type, and then for each of those models, retrieve all of its submodels (getSubmodels). Once they're all fetched I place them into a JSON object. I'm trying to use Angular's $q module to ensure that all submodels have been retrieved before I continue execution - aka I need all of the promises to be resolved before I can move on and render the page components.
Issue:
The 'happy' path works perfectly, but if one of the requests for submodels has an error, the rejected promise from the submodel function does not get caught by the $q.all().then block, and thus the overall getVehicleHierarchy promise does not get rejected.
angular.module('uiApp.services.hierarchy', ['restangular'])
.service('VehicleHierarchy', function VehicleHierarchy($http, $q, Restangular) {
this.getVehicleHierarchy = function (manufacturerId, vehicleTypeId, vehicleModelId) {
var that = this;
var deferred = $q.defer();
var promise = deferred.promise;
var promises = [];
Restangular.configuration.baseUrl = urlBuilder.buildHierarchyServiceUrl() + '/vehicle-hierarchy';
Restangular.one('manufacturer', manufacturerId).one('type', vehicleTypeId).one('class', vehicleClassId).customGET('models')
.then(function (models) {
var result = {};
_.forEach(models.result, function (model) {
result[parseInt(model.id)] = model;
});
_.forEach(result, function (model) {
promises.push(that.getSubmodels(manufacturerId, vehicleTypeId, vehicleClassId, model.id));
});
$q.all(promises).then(function (results) {
var i = 0;
_.forEach(result, function (model) {
result[parseInt(model.id)].subModels = results[i++];
}, function (errors) {
deferred.reject(errorResponse);
});
deferred.resolve(result);
});
}, function(error) {
deferred.reject('error!');
});
return promise;
};
this.getSubmodels = function (manufacturerId, vehicleTypeId, vehicleClassId, modelId) {
var submodels = {};
var deferred = $q.defer();
Restangular.configuration.baseUrl = urlBuilder.buildHierarchyServiceUrl() + '/vehicle-hierarchy';
Restangular.one('manufacturer', brandId).one('type', vehicleTypeId).one('class', vehicleClassId).one('model', modelId)
.customGET('submodels').then(function (submodelResponse) {
_.forEach(subclassResponse.result, function (subModel) {
subclasses[parseInt(subModel.id)] = subModel;
});
deferred.resolve(subclasses);
}, function (error) {
deferred.reject('error'!);
});
return deferred.promise;
};
});
});
You need to return the $q.all call for your error function in the then to catch it:
return $q.all(promises).then(function (results) {
I have an $http request that is returning a bunch of rows. I need to process each of those results synchronously. Having trouble wrapping my brain around Angular.
Each of the records needs to be processed against a local SQLite database on an iOS device, and that is an asynchronous call.
If any of the loop records fail, I need to abort the entire operation (and loop).
Here's the code to see if it helps...
var username = $rootScope.currentUser;
window.logger.logIt("Executing incremental sync with username " + username);
var url = $rootScope.serviceBaseUrl + 'SyncData/GetSyncItems?userid=' + username + '&lastSyncDate=' + lastSyncDate.toString();
var encoded = encoder.encode($CONFIG.serviceAccount);
$http.defaults.headers.common.Authorization = 'Basic ' + encoded;
$http({ method: 'Get', url: url })
.success(function(data, status, headers, config) {
var processes = [];
for (var i in data) {
var params = data[i].Params;
var paramsMassaged = params.replaceAll("[", "").replaceAll("]", "").replaceAll(", ", ",").replaceAll("'", "");
var paramsArray = paramsMassaged.split(",");
var process;
if (data[i].TableName === "Tabl1") {
window.logger.logIt("setting the process for a Table1 sync item");
process = $Table1_DBContext.ExecuteSyncItem(data[i].Query, paramsArray);
} else if (data[i].TableName === "Table2") {
window.logger.logIt("setting the process for an Table2 sync item");
process = $Table2_DBContext.ExecuteSyncItem(data[i].Query, paramsArray);
} else {
window.logger.logIt("This table is not included in the sync process. You have an outdated version of the application. Table: " + data[i].TableName);
}
window.logger.logIt("got to here...");
processes.push(process);
}
window.logger.logIt("Finished syncing all " + data.length + " records in the list...");
$q.all(processes)
.then(function (result) {
// Update the LastSyncDate here
$DBConfigurations_DBContext.UpdateLastSyncDate(data[i].CreatedDate);
alert("finished syncing all records");
}, function (result) {
alert("an error occurred.");
});
})
.error(function(data, status, headers, config) {
alert("An error occurred retrieving the items that need to be synced.");
});
Table2 ExecuteSyncItem function:
ExecuteSyncItem: function (script, params) {
//window.logger.logIt("In the Table2 ExecuteSyncItem function...");
//$DBService.ExecuteQuery(script, params, null);
var deferred = $q.defer();
var data = $DBService.ExecuteQuery(script, params, null);
if (data) {
deferred.resolve(data);
} else {
deferred.reject(data);
}
return deferred.promise;
}
DB Service code:
ExecuteQuery: function (query, params, success) {
$rootScope.db.transaction(function (tx) {
tx.executeSql(query, params, success, onError);
});
},
Update: In response to Maxim's question "did you log process method". Here's what I'm doing...
ExecuteSyncItem: function (script, params) {
window.logger.logIt("In the Experiment ExecuteSyncItem function...");
//$DBService.ExecuteQuery(script, params, null);
var deferred = $q.defer();
var data = $DBService.ExecuteQuery(script, params, function () { window.logger.logIt("successCallback"); });
if (data) {
window.logger.logIt("success");
deferred.resolve(data);
} else {
window.logger.logIt("fail");
deferred.reject(data);
}
return deferred.promise;
}
"data" is undefined everytime. "fail" is logged everytime, as well as "successCallback". Also, the executeQuery IS working, and updating the data the way I expect.
So now, it's just a matter of the promise syntax I guess. If the ExecuteQuery isn't actually populating the "data" variable since it's asynchronous, how do I set the deferred.resolve() and deferred.reject stuff?
You are on right way
I would use $q.all
$q.all([async1(), async2() .....])
Combines multiple promises into a single promise that is resolved when all of the input promises are resolved.
Returns a single promise that will be resolved with an array/hash of values, each value corresponding to the promise at the same index/key in the promises array/hash. If any of the promises is resolved with a rejection, this resulting promise will be rejected with the same rejection value.
For example:
var processes = [];
processes.push(Process1);
processes.push(Process2);
/* ... */
$q.all(processes)
.then(function(result)
{
/* here all above mentioned async calls finished */
$scope.response_1 = result[0];
$scope.response_2 = result[1];
}, function (result) {
alert("Error: No data returned");
});
From your example you run in loop and call async methods (Process1, Process2) 10 times (8 and 2 respectively). In order to use $q.all the Process1, Process2 must return promise.
So I would write it something like that:
var Process1 = function(stuff) {
var deferred = $q.defer();
var data = $DBService.ExecuteQuery(stuff.query); // This is asynchronous
if (data ) {
deferred.resolve(data);
} else {
deferred.reject(data);
}
return deferred.promise;
}