Apply PromiseJS to this situation - javascript

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
});
}

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)
})
})
}

Chaining Multiple Optional Async Ajax Requests

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;
});

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
})

Node return BluebirdJS Promise

I have a small problem, this script works perfectly, with one problem, the "runTenant" method is not returning a promise (that needs resolving from "all()".
This code:
Promise.resolve(runTenant(latest)).then(function() {
end();
});
Calls this code:
function runTenant(cb) {
return new Promise(function() {
//global var
if (!Tenant) {
loadCoreModels();
Tenant = bookshelf.core.bs.model('Tenant');
}
new Tenant().fetchAll()
.then(function(tenants) {
if (tenants.models.length == 0) {
return;
} else {
async.eachSeries(tenants.models, function(tenant, next) {
var account = tenant.attributes;
Promise.resolve(db_tenant.config(account)).then(function(knex_tenant_config) {
if (knex_tenant_config) {
db_tenant.invalidateRequireCacheForFile('knex');
var knex_tenant = require('knex')(knex_tenant_config);
var knex_pending = cb(knex_tenant);
Promise.resolve(knex_pending).then(function() {
next(null, null);
});
} else {
next(null, null);
}
});
});
};
});
});
}
The code from runTenant is working correctly however it stalls and does not proceed to "end()" because the promise from "runTenant(latest)" isn't being resolved.
As if it weren't apparent, I am horrible at promises. Still working on getting my head around them.
Many thanks for any help/direction!
You should not use the Promise constructor at all here (and basically, not anywhere else either), even if you made it work it would be an antipattern. You've never resolved that promise - notice that the resolve argument to the Promise constructor callback is a very different function than Promise.resolve.
And you should not use the async library if you have a powerful promise library like Bluebird at hand.
As if it weren't apparent, I am horrible at promises.
Maybe you'll want to have a look at my rules of thumb for writing promise functions.
Here's what your function should look like:
function runTenant(cb) {
//global var
if (!Tenant) {
loadCoreModels();
Tenant = bookshelf.core.bs.model('Tenant');
}
return new Tenant().fetchAll().then(function(tenants) {
// if (tenants.models.length == 0) {
// return;
// } else
// In case there are no models, the loop iterates zero times, which makes no difference
return Promise.each(tenants.models, function(tenant) {
var account = tenant.attributes;
return db_tenant.config(account).then(function(knex_tenant_config) {
if (knex_tenant_config) {
db_tenant.invalidateRequireCacheForFile('knex');
var knex_tenant = require('knex')(knex_tenant_config);
return cb(knex_tenant); // can return a promise
}
});
});
});
}
Your promise in runTenant function is never resolved. You must call resolve or reject function to resolve promise:
function runTenant() {
return new Promise(function(resolve, reject) {
// somewhere in your code
if (err) {
reject(err);
} else {
resolve();
}
});
});
And you shouldn't pass cb in runTenant function, use promises chain:
runTenant()
.then(latest)
.then(end)
.catch(function(err) {
console.log(err);
});
You need to return all the nested promises. I can't run this code, so this isn't a drop it fix. But hopefully, it helps you understand what is missing.
function runTenant(cb) {
//global var
if (!Tenant) {
loadCoreModels();
Tenant = bookshelf.core.bs.model('Tenant');
}
return new Tenant().fetchAll() //added return
.then(function (tenants) {
if (tenants.models.length == 0) {
return;
} else {
var promises = []; //got to collect the promises
tenants.models.each(function (tenant, next) {
var account = tenant.attributes;
var promise = Promise.resolve(db_tenant.config(account)).then(function (knex_tenant_config) {
if (knex_tenant_config) {
db_tenant.invalidateRequireCacheForFile('knex');
var knex_tenant = require('knex')(knex_tenant_config);
var knex_pending = cb(knex_tenant);
return knex_pending; //return value that you want the whole chain to resolve to
}
});
promises.push(promise); //add promise to collection
});
return Promise.all(promises); //make promise from all promises
}
});
}

Bluebird Promising the result of a heavy function

I've been using Bluebird a lot recently on a HAPI API development. I've just run into my first real problem, that perhaps my understanding or naivety has me stumped.
The following code is an example of what I am facing:-
var Promise = require('bluebird'),
stuff = require('../stuff');
module.exports = {
getSomething: function(request, reply) {
var p = Promise.resolve();
p = p.then(function() {
return db.find() //etc. etc.
});
p = p.then(function(resultFromPromise) {
//problems begin here
var data = stuff.doSomeReallyLongAndBoringFunction(resultFromPromise);
return data;
});
p.then(function(data) {
//no data here.
});
};
};
I've commented where the problems usually begin. the stuff.doSomeReallyLongAndBoringFunction() returns an object (using more promises concidently) and it's this object I want to access, but //no data here always fires before data returns. stuff.doSomeReallyLongAndBoringFunction() continues to run regardless and completes successfully, but after the code goes async, I don't know how to promise that function's return value back.
Can anyone offer any guidance? Please accept my apologies for any naivety in the question!
Help as always, is appreciated
NB just for clarity, stuff.doSomeReallyLongAndBoringFunction() does not return a Promise itself. Although, I did try return new Promise(reject, resolve) { }); manual wrap. It is simply a function that uses promises itself (successfully) to get data.
Update 1
stuff.doSomeReallyLongAndBoringFunction() is too big to post directly, but it does something like this:-
var Promise = require('bluebird'),
rp = require('request-promise');
module.exports = {
doSomeReallyLongAndBoringFunction: function() {
var p = Promise.resolve();
p = p.then(function() {
return db.find() //etc. etc.
});
p.then(function() {
rp(options).then(function(response){
//get some data from remote location
}).then(function(dataFromService) {
//do some jiggery pokery with said data
var marshalledData = dataFromService;
db.something.create({
Field: 'something'
}).exec(function(err, saved) {
return marshalledData;
});
});
}).catch(function(err) {
});
};
};
Update 2
Thank you Justin for your help. Here is the actual code, perhaps this may help?
Promise.resolve()
.then(function() {
if(typeof utils.intTryParse(place) !== 'number') {
return foursquare.createPlaceFromFoursquare(sso, place, request, reply);
} else {
return { Place: { PlaceId: place }};
}
}).then(function(placeObj) {
console.log('Place set as', placeObj); //always returns undefined, despite function actually completing after async op...
});
If your doSomeReallyLongAndBoringFunction is really running asynchronously, then it doesn't make sense to run it the way you have setup.
Edit - Here's a simple explanation of the way your code looks to be running vs a refactored version. It's been simplified , so you'll need to fill in the relevant sections with your actual implementation.
var Promise = require('bluebird');
function myAsync() {
setTimeout(function(){
return 'done sleeping';
}, 2000);
};
//The way your code is running
Promise.resolve()
.then(function(){
return 'hello';
})
.then(function(done){
console.log(done);
return myAsync(); //your error is here
})
.then(function(done){
console.log(done);
});
//refactored
Promise.resolve()
.then(function(){
return 'hello';
})
.then(function(done){
console.log(done);
return new Promise(function(resolve) {
setTimeout(function(){
resolve('done sleeping');
}, 2000);
});
})
.then(function(done){
console.log(done);
});
just for clarity, stuff.doSomeReallyLongAndBoringFunction() does not return a Promise itself.
And that's your problem. As it does something asynchronous and you want to get its result, it should return a promise. In fact, that's the case for every asynchronous function, especially then callbacks! It should be something like
module.exports = {
doSomeReallyLongAndBoringFunction: function() {
return db.find()
// ^^^^^^
.then(function() {
return rp(options).then(function(response){
// ^^^^^^
//get some data from remote location
}).then(function(dataFromService) {
//do some jiggery pokery with said data
var marshalledData = dataFromService;
return db.something.create({
// ^^^^^^
Field: 'something'
}).execAsyc();
});
}).catch(function(err) {
});
}
};
Your getSomething method has the same issues, and should look like this:
var createPlace = Promise.promisify(foursquare.createPlaceFromFoursquare);
module.exports = {
getSomething: function(request) {
var p;
if (typeof utils.intTryParse(place) !== 'number')
p = createPlace(sso, place, request); // this returns a promise!
else
p = Promise.resolve({Place: {PlaceId: place}});
return p.then(function(placeObj) {
// ^^^^^^
console.log('Place set as', placeObj);
});
}
};
See also these generic rules for promise development.
doSomeReallyLongAndBoringFunction needs to look like this:
doSomeReallyLongAndBoringFunction: function(param) {
var resolver = Promise.defer();
/*
* do some asynchronous task and when you are finished
* in the callback, do this:
*/
resolver.resolve(resultFromAsyncTask);
/*
*
*
*/
return resolver.promise;
}

Categories