Chaining Multiple Optional Async Ajax Requests - javascript

I'm using Angular 1.5.8. The views in my app require different combinations of the same 3 ajax requests. Some views require data from all three, others require data from two, or even one single endpoint.
I'm working on a function that will manage the retrieval of this data, requiring the app to only call each endpoint once. I want the ajax requests to be called as needed, but only when needed. Currently I've created a function which works, but seems like it could use improvement.
The following function is contained within the $rootScope. It uses the fetchData() function to cycle through the get requests as requested. When data is retrieved, it is stored in the global variable $rootScope.appData and then fetchData() is called again. When all data is retrieved the deferred promise is resolved and the data is returned to the controller.
$rootScope.appData = {};
$rootScope.loadAppData = function(fetch) {
var deferred = $q.defer();
function getUser() {
$http
.get('https://example.com/api/getUser')
.success(function(result){
$rootScope.appData.currentUser = result;
fetchData();
});
}
function getPricing() {
$http
.get('https://example.com/api/getPricing')
.success(function(result) {
$rootScope.appData.pricing = result;
fetchData();
});
}
function getBilling() {
$http
.get('https://example.com/api/getBilling')
.success(function(result) {
$rootScope.appData.billing = result;
fetchData();
});
}
function fetchData() {
if (fetch.user && !$rootScope.appData.currentUser) {
getUser();
} else if (fetch.pricing && !$rootScope.appData.pricing) {
getPricing();
} else if (fetch.billing && !$rootScope.appData.billing) {
getBilling();
} else {
deferred.resolve($rootScope.appData);
}
}
if ($rootScope.appData.currentUser && $rootScope.appData.pricing &&$rootScope.appData.billing) {
deferred.resolve($rootScope.appData);
} else {
fetchData();
}
return deferred.promise;
};
An object fetch is submitted as an attribute, this object shows which ajax requests to call. An example call to the $rootScope.loadAppData() where only user and pricing data would be requested would look like this:
$rootScope.loadAppData({user: true, pricing: true}).then(function(data){
//execute view logic.
});
I'm wondering:
Should the chaining of these functions be done differently? Is the fetchData() function sufficient, or is this an odd way to execute this functionality?
Is there a way to call all needed Ajax requests simultaneously, but wait for all required calls to complete before resolving the promise?
Is it unusual to store data like this in the $rootScope?
I'm aware that this function is not currently handling errors properly. This is functionality I will add before using this snippet, but isn't relevant to my question.

Instead of using the .success method, use the .then method and return data to its success handler:
function getUserPromise() {
var promise = $http
.get('https://example.com/api/getUser')
.then( function successHandler(result) {
//return data for chaining
return result.data;
});
return promise;
}
Use a service instead of $rootScope:
app.service("myService", function($q, $http) {
this.loadAppData = function(fetchOptions) {
//Create first promise
var promise = $q.when({});
//Chain from promise
var p2 = promise.then(function(appData) {
if (!fetchOptions.user) {
return appData;
} else {
var derivedPromise = getUserPromise()
.then(function(user) {
appData.user = user;
//return data for chaining
return appData;
});
return derivedPromise;
);
});
//chain from p2
var p3 = p2.then(function(appData) {
if (!fetchOptions.pricing) {
return appData;
} else {
var derivedPromise = getPricingPromise()
.then(function(pricing) {
appData.pricing = pricing;
//return data for chaining
return appData;
});
return derivedPromise;
);
});
//chain from p3
var p4 = p3.then(function(appData) {
if (!fetchOptions.billing) {
return appData;
} else {
var derivedPromise = getBillingPromise()
.then(function(user) {
appData.billing = billing;
//return data for chaining
return appData;
});
return derivedPromise;
);
});
//return final promise
return p4;
}
});
The above example creates a promise for an empty object. It then chains three operations. Each operations checks to see if a fetch is necessary. If needed a fetch is executed and the result is attached to the appData object; if no fetch is needed the appData object is passed to the next operation in the chain.
USAGE:
myService.loadAppData({user: true, pricing: true})
.then(function(appData){
//execute view logic.
}).catch(functon rejectHandler(errorResponse) {
console.log(errorResponse);
throw errorResponse;
});
If any of the fetch operations fail, subsequent operations in the chain will be skipped and the final reject handler will be called.
Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises. It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs. -- AngularJS $q Service API Reference - Chaining Promises

Found a good way to answer question 2 in the original post. Using $q.all() allows the promises to execute simultaneously, resolving once they all complete, or failing as soon as one of them fails. I've added this logic into a service thanks to #georgeawg. Here's my re-write putting this code into a service, and running all calls at the same time:
services.factory('appData', function($http, $q) {
var appData = {};
var coreData = {};
appData.loadAppData = function(fetch) {
var deferred = $q.defer();
var getUser = $q.defer();
var getPricing = $q.defer();
var getBilling = $q.defer();
if (!fetch.user || coreData.currentUser) {
getUser.resolve();
} else {
$http
.get('https://example.com/api/getUser')
.success(function(result){
coreData.currentUser = result;
getUser.resolve();
}).error(function(reason) {
getUser.reject(reason);
});
}
if (!fetch.billing || coreData.billing) {
getBilling.resolve();
} else {
$http
.get('https://example.com/api/getBilling')
.success(function(result) {
coreData.billing = result;
getBilling.resolve();
}).error(function(reason) {
getBilling.reject(reason);
});
}
if (!fetch.pricing || coreData.pricing) {
getPricing.resolve();
} else {
$http
.get('https://example.com/api/getPricing')
.success(function(result) {
coreData.pricing = result;
getPricing.resolve();
}).error(function(reason) {
getPricing.reject(reason);
});
}
$q.all([getPricing.promise, getUser.promise, getBilling.promise]).then(function(result) {
deferred.resolve(coreData);
}, function(reason){
deferred.reject(reason);
});
return deferred.promise;
};
return appData;
});

Related

Chaining Promises and Passing Parameters between Them

I'm new to Node/Express and am trying to use Promises to executive successive API calls to Apple's CloudKit JS API.
I'm unclear on how to put the functions in sequence and pass their respective return values from one function to the next.
Here's what I have so far:
var CloudKit = require('./setup')
//----
var fetchUserRecord = function(emailConfirmationCode){
var query = { ... }
// Execute the query
CloudKit.publicDB.performQuery(query).then(function (response) {
if(response.hasErrors) {
return Promise.reject(response.errors[0])
}else if(response.records.length == 0){
return Promise.reject('Email activation code not found.')
}else{
return Promise.resolve(response.records[0])
}
})
}
//-----
var saveRecord = function(record){
// Update the record (recordChangeTag required to update)
var updatedRecord = { ... }
CloudKit.publicDB.saveRecords(updatedRecord).then(function(response) {
if(response.hasErrors) {
Promise.reject(response.errors[0])
}else{
Promise.resolve()
}
})
}
//----- Start the Promise Chain Here -----
exports.startActivation = function(emailConfirmationCode){
CloudKit.container.setUpAuth() //<-- This returns a promise
.then(fetchUserRecord) //<-- This is the 1st function above
.then(saveRecord(record)) //<-- This is the 2nd function above
Promise.resolve('Success!')
.catch(function(error){
Promise.reject(error)
})
}
I get an error near the end: .then(saveRecord(record)) and it says record isn't defined. I thought it would somehow get returned from the prior promise.
It seems like this should be simpler than I'm making it, but I'm rather confused. How do I get multiple Promises to chain together like this when each has different resolve/reject outcomes?
There are few issues in the code.
First: you have to pass function to .then() but you actually passes result of function invocation:
.then(saveRecord(record))
Besides saveRecord(record) technically may return a function so it's possible to have such a statement valid it does not seem your case. So you need just
.then(saveRecord)
Another issue is returning nothing from inside saveRecord and fetchUserRecord function as well.
And finally you don't need to return wrappers Promise.resolve from inside .then: you may return just transformed data and it will be passed forward through chaining.
var CloudKit = require('./setup')
//----
var fetchUserRecord = function(emailConfirmationCode){
var query = { ... }
// Execute the query
return CloudKit.publicDB.performQuery(query).then(function (response) {
if(response.hasErrors) {
return Promise.reject(response.errors[0]);
}else if(response.records.length == 0){
return Promise.reject('Email activation code not found.');
}else{
return response.records[0];
}
})
}
//-----
var saveRecord = function(record){
// Update the record (recordChangeTag required to update)
var updatedRecord = { ... }
return CloudKit.publicDB.saveRecords(updatedRecord).then(function(response) {
if(response.hasErrors) {
return Promise.reject(response.errors[0]);
}else{
return Promise.resolve();
}
})
}
//----- Start the Promise Chain Here -----
exports.startActivation = function(emailConfirmationCode){
return CloudKit.container.setUpAuth() //<-- This returns a promise
.then(fetchUserRecord) //<-- This is the 1st function above
.then(saveRecord) //<-- This is the 2nd function above
.catch(function(error){});
}
Don't forget returning transformed data or new promise. Otherwise undefined will be returned to next chained functions.
Since #skyboyer helped me figure out what was going on, I'll mark their answer as the correct one.
I had to tweak things a little since I needed to pass the returned values to subsequent functions in my promise chain. Here's where I ended up:
exports.startActivation = function(emailConfirmationCode){
return new Promise((resolve, reject) => {
CloudKit.container.setUpAuth()
.then(() => {
return fetchUserRecord(emailConfirmationCode)
})
.then((record) => {
resolve(saveRecord(record))
}).catch(function(error){
reject(error)
})
})
}

Unit Testing of chain of promises with Jasmine

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.

Method chaining and promises issue in Angular app with regards to returning a compared and updated array

I am trying to filter/match a list of returned IDs to a list of JSON data records, but I am struggling with (I believe) my promises and method chaining.
I can get the functions to work, except for when I add step 3 below. Then it resolves without the matching data (the function does carry on and eventually return the correct matching data, but by that time my method has already completed).
This is how it is supposed to work:
(getCompanyBrandProfileIDs) First my method gets a brandProfileID linked to the current user.
(getBrandProfiles) Then it takes the brandProfileID and get all brandProfiles linked to the specific brandProfile.
(getKeywordProfiles) Then it SHOULD take the returned brandProfiles, and get the matching keywordProfile for each brandProfile. It is an array of objects containing a brand_profile_id and and id.
This is my main method:
this.getCompanyBrandProfileIDs = function () {
var brandProfileIDsToReturn = $q.defer();
GetUserAccessService.returnBrandProfileID().then(function (brandProfileID) {
console.log(brandProfileID);
getBrandProfiles(brandProfileID).then(function (brandProfiles) {
console.log(JSON.stringify(brandProfiles));
var keywordProfilesArray = [];
getKeywordProfiles(brandProfiles).then(function (keywordProfiles) {
keywordProfilesArray = keywordProfiles;
console.log(JSON.stringify(keywordProfilesArray));
//brandProfileIDsToReturn.resolve(keywordProfilesArray);
});
brandProfileIDsToReturn.resolve(keywordProfilesArray);
});
});
return brandProfileIDsToReturn.promise;
};
This is the getBrandProfiles method:
function getBrandProfiles(brandProfileID) {
var getBrandProfilesLinkedToCompany = $q.defer();
pullSocialMediaData('keyword_profile_brand_profiles.json?brand_profile_id=' + brandProfileID).then(function (brandProfiles) {
var brandProfilesArray = [];
brandProfiles.forEach(function (profile) {
brandProfilesArray.push({ id: profile.id, name: profile.name });
});
getBrandProfilesLinkedToCompany.resolve(brandProfilesArray);
});
return getBrandProfilesLinkedToCompany.promise;
}
This is the getKeywordProfiles method:
function getKeywordProfiles(brandProfiles) {
var keywordProfilesToReturn = $q.defer();
var brandProfilesArray = brandProfiles;
var array = [];
brandProfilesArray.forEach(function (profile) {
findKeywordProfile(profile.id).then(function (keywordID) {
array.push(keywordID);
});
keywordProfilesToReturn.resolve(array);
})
return keywordProfilesToReturn.promise;
}
This is a helper method for getKeywordProfiles:
function findKeywordProfile(brandProfileID) {
var keywordProfileID = $q.defer();
pullSocialMediaData('list_keyword_profiles.json').then(function (data) {
var keywordProfileInstance = data.filter(function (keyword) {
return keyword.brand_profile_id === brandProfileID;
});
keywordProfileID.resolve(keywordProfileInstance[0].id);
});
return keywordProfileID.promise;
}
I would appreciate your assistance!
You are resolving the brandProfileIDsToReturn too soon. In this code: when you resolve the promise the then callback will not have been called, so keywordProfilesArray is guaranteed to be empty.
this.getCompanyBrandProfileIDs = function () {
var brandProfileIDsToReturn = $q.defer();
GetUserAccessService.returnBrandProfileID().then(function (brandProfileID) {
console.log(brandProfileID);
getBrandProfiles(brandProfileID).then(function (brandProfiles) {
console.log(JSON.stringify(brandProfiles));
var keywordProfilesArray = [];
getKeywordProfiles(brandProfiles).then(function (keywordProfiles) {
keywordProfilesArray = keywordProfiles;
console.log(JSON.stringify(keywordProfilesArray));
//brandProfileIDsToReturn.resolve(keywordProfilesArray);
});
brandProfileIDsToReturn.resolve(keywordProfilesArray);
});
});
return brandProfileIDsToReturn.promise;
};
Simply moving the resolve() call inside the then callback should fix it and in fact you have that line commented out, so uncomment it and remove the other resolve:
this.getCompanyBrandProfileIDs = function () {
var brandProfileIDsToReturn = $q.defer();
GetUserAccessService.returnBrandProfileID().then(function (brandProfileID) {
console.log(brandProfileID);
getBrandProfiles(brandProfileID).then(function (brandProfiles) {
console.log(JSON.stringify(brandProfiles));
var keywordProfilesArray = [];
getKeywordProfiles(brandProfiles).then(function (keywordProfiles) {
keywordProfilesArray = keywordProfiles;
console.log(JSON.stringify(keywordProfilesArray));
brandProfileIDsToReturn.resolve(keywordProfilesArray);
});
});
});
return brandProfileIDsToReturn.promise;
};
However you can probably simplify the code a lot if you stop using $q.defer(). Your functions already return promises so just return the promises they use and stop trying to create additional promises. I think this is equivalent to the previous code except it returns the promises directly, and I removed the log messages, and that means the getKeywordProfiles call simplifies down to a callback that just calls the function so you can pass the function directly:
this.getCompanyBrandProfileIDs = function () {
return GetUserAccessService.returnBrandProfileID().then(function (brandProfileID) {
return getBrandProfiles(brandProfileID).then(getKeywordProfiles);
});
});
};
and then you can simplify it further by extracting the inner .then:
this.getCompanyBrandProfileIDs = function () {
return GetUserAccessService.returnBrandProfileID()
.then(getBrandProfiles)
.then(getKeywordProfiles);
};
The getKeywordProfiles() function also needs to avoid resolving its promise until all of the findKeywordProfile() calls have resolved. Return a promise for the array of promises and when they resolve the promise will complete to an array of values:
function getKeywordProfiles(brandProfilesArray) {
var array = [];
brandProfilesArray.forEach(function (profile) {
array.push(findKeywordProfile(profile.id));
})
return $q.all(array);
}
To clarify my comments about $q, there are some cases where you need to create a promise from scratch using it, but they're fairly uncommon. Anything that happens asynchronously in Angular already returns a promise, and the great thing about promises is that they chain together, so when you have one promise calling .then() or .catch() will return a new one. Also the .then() callback can either return a value which resolves the new promise, or can return a promise which will only resolve the new promise when it, itself resolves. So just keep chaining the .then() calls together and each will wait for the previous one to complete.
$q is still useful though: $q.all() if you want to wait for a bunch of promises to all resolve, $q.race() if you have a bunch of promises and only one needs to resolve, $q.resolve(value) can also be useful as sometimes you just want a promise that will resolve immediately so you can hang a chain of other async functions off it.
Also it is safe to keep a promise around even long after it has resolved and you can still call .then() on it: this is useful if you have asynchronous initialisation code and events that may or may not be triggered before the initialisation has completed. No need to do if(isInitialised) when you can just do initPromise.then(...)
In getKeywordProfiles function you need resolve it when array loop finished.
function getKeywordProfiles(brandProfiles) {
var keywordProfilesToReturn = $q.defer();
var brandProfilesArray = brandProfiles;
var array = [];
brandProfilesArray.forEach(function (profile) {
findKeywordProfile(profile.id).then(function (keywordID) {
array.push(keywordID);
});
//--
//keywordProfilesToReturn.resolve(array);
})
//++
keywordProfilesToReturn.resolve(array);
return keywordProfilesToReturn.promise;
}
Info: I think you need to create an profileIdArray push all brandProfileID and send to your findKeywordProfile function. It will be more useful.

Nodejs Promise for custom api

I am new to nodejs and using promise and actually this is my first real app with nodejs.
So i have been reading all day and i am a bit confused.
So this is my module :
function User() {
var self = this;
self.users = {};
self.start = function (user, botId) {
return new Promise(function () {
return get(user).then(function (data) {
debug(data);
if (data.botId.indexOf(botId) === false) {
return Repo.UserBotModel.addUser(user.id, botId).then(function () {
data.botId.push(botId);
return data;
});
} else
return data;
});
});
};
self.getDisplayName = function (user) {
if (user.real_name)
return user.real_name;
if (user.last_name)
return user.firstname + ' ' + user.last_name;
return user.first_name;
};
/**
* check if user exist in our database/memory cache and return it,
* otherwise insert in the database and cache it in memory and the return it
* #param user
*/
function get(user) {
return new Promise(function () {
//check if user is loaded in our memory cache
if (self.users.hasOwnProperty(user.id))
return self.users[user.id];
else {
//get from database if exist
return Repo.UserModel.get(user.id).then(function (rows) {
if (rows && rows.length) {
//user exist cache it and resolve
var data = rows[0];
if (data.botId && data.botId.length)
data.botId = data.botId.split(',');
else
data.botId = [];
self.users[user.id] = data;
//------------------------------ code execution reaches here
return data;
}
else {
//user dose not exist lets insert it
return Repo.UserModel.insert(user).then(function (result) {
return get(user);
});
}
});
}
});
}
}
I call the start method witch calls the private get method the call reaches return data;(marked with comment) but then function dose not gets executed in the start method ???
So what am i doing wrong?
UPDATE : Sorry I forgot to mention that I am using bluebird and not the native promise if that makes a difference?
You cannot return from the Promise constructor - you have to call resolve (expected to happen asynchronously). You're not supposed to use the Promise constructor at all here. You can just omit it, and it should work.
The methods from your Repo.UserModel already return promises, so you do not have to create new ones using new Promise.
You can read the values inside those promises using then.
then also provides a way to transform promises. If you return a value in a function passed to then, then will return a new promise that wraps the value you returned. If this value is a promise, it will be awaited.
To convert a value to a promise, you can use Promise.resolve.
Knowing that, you can simplify get like so:
function get(user) {
if (...) {
return Promise.resolve(...)
} else {
return Repo.UserModel.get(...).then(function(rows) {
...
return ...
})
}
}
This version of getwill always return a promise that you can use like so:
get(...).then(function(resultOfGet) {
// process resultOfGet
})

Apply PromiseJS to this situation

I've just started using js modules with node, require and browserify and trying get code to work, that was originally in a single script.
I'd also like to start using promisejs but am unsure how to apply it in this situation.
All the require browserify parts seem to work so I'll leave that out all the none relevant bits for simplicity.
In the one module I have something like this
module.exports.api = function(){
var getCurrentProcessInstanceTask = function getCurrentProcessInstanceTask(options) {
if (!currentProcess || !currentProcess.id) {
throw new Error("no currentProcess is set, cannot get active task");
return;
}
var processInstanceId = currentProcess.id;
jQuery.get(hostUrl + "service/runtime/tasks", {
processInstanceId: processInstanceId
})
.done(function(data) {
console.log("response: " + JSON.stringify(data, null, 2));
currentProcess.tasks = data.data;
if (options && options.callback) {
options.callback(data.data);
}
});
}
return {
getCurrentProcessInstanceTask: getCurrentProcessInstanceTask
}
}
Then in the other module I have something like this
var Api = require('./api');
module.exports.view = function(){
var api = Api();
var setupEmbeddedView = function setupEmbeddedView(url, tmpl) {
tmpls.renderExtTemplate({
name: tmpl,
selector: targetDiv,
data: {
url: url,
width: iframeTargetDiv.width(),
height: iframeTargetDiv.height()
},
callback: function() {
jQuery('#taskFormFrame').load(function(e) {
console.log("taskFormFrame load fired!");
});
}
});
},
showCurrentTaskForm = function showCurrentTaskForm() {
console.log("mark");
api.getCurrentProcessInstanceTask({
callback: function(tasks) {
setupEmbeddedView(getTaskFormUrl(tasks), 'showTaskForm');
}
});
}
return {
showCurrentTaskForm: showCurrentTaskForm
}
}
calling showCurrentTaskForm in another module where view is required results in the api.getCurrentProcessInstanceTask part being executed but setupEmbeddedView never seems to get called.
I'm very confused about why maybe someone can explain.
Also I'd like an example of how I would apply promisejs in this particular case instead of using callbacks to chain functions
Current version of jQuery does not fulfill the Promises/A+ specification. I would recommend the use of a separate promise library, such as bluebird.
Each function that runs an asynchronous block of code should return a promise.
Looking at your code, the getCurrentProcessInstanceTask and thus it should return a promise.
Since you are using jQuery.get() which returns a jQuery promise, you will have to let bluebird assimilate that promise to create a proper promise chain.
var getCurrentProcessInstanceTask = function getCurrentProcessInstanceTask() {
if (!currentProcess || !currentProcess.id) {
return Promise.reject(new Error("no currentProcess is set, cannot get active task"))
}
var processInstanceId = currentProcess.id;
return Promise.resolve(jQuery.get(hostUrl + "service/runtime/tasks", {processInstanceId: processInstanceId})
.then(function(data){
console.log("response: " + JSON.stringify(data, null, 2));
currentProcess.tasks = data.data;
return data.data;
});
);
}
To run it sequentially, just modify the showCurrentTaskForm function to call the api function and .then() the returned promise.
showCurrentTaskForm = function showCurrentTaskForm() {
console.log("mark");
api.getCurrentProcessInstanceTask()
.then(function(result){
setupEmbeddedView(result, 'showTaskForm');
})
.catch(function(e){
//handle the error
});
}

Categories