So i brought to life this abomination and i couldnt for the life of me get my head around how to optimise it in such a way i can run this chain properly using Promise.all / Promise.join.
Anyone able to point me in the right direction? Should probably separate the methods first.
Any insight is appreciated.
getOpenIDConf: function() {
return client
.getAsync('openId')
.then(
function(result) {
if (!result) {
return request
.getAsync({
url: 'https://accounts.google.com/.well-known/openid-configuration',
json: true
}).spread(
function(response, body) {
var result = JSON
.stringify(body);
client.setAsync('openId',
result).then(
function() {
return result;
});
});
} else {
return result;
}
});
},
[EDIT] To clarify, i'm using bluebird
Refactoring a bit and changing the code style gives this.
getOpenIDConf: () => client.getAsync('openId').then(result =>
result || request.getAsync({
url: 'https://accounts.google.com/.well-known/openid-configuration',
json: true
}).get(1).then(JSON.stringify).then(result =>
client.setAsync('openId', result).return(result);
)
)
},
A few features of a good promise library (not sure which one you are using) is that you can chain the promises like so:
doSomething(function(result) {
return doSomethingElse();
}).then(function(result2) {
return doSomethingElseAgain();
}).then(function(result3) {
// It all worked!
}).catch(function() {
// Something went wrong
});
Or you can wait for a set of them to complete:
var promiseArray = [];
promiseArray.push(doSomething());
promiseArray.push(doSomethingElse());
promiseArray.push(doSomethingElseAgain());
Promise.all(promiseArray).then(function() {
// It all worked!
}).catch(function() {
// Something went wrong
});
Hope this is informative.
Related
Given the code below, how can I pass id to the applySaveAsync function?
var then = _.curry(function (f, thenable) {
return thenable.then(f);
});
var validateAsync = _.flow(
function () { return _(someCondition).showError(ERROR_01).value(); },
then(function () { return _(anotherCondition).showError(ERROR_02).value(); })
);
var save = _.flow(
validateAsync,
then(applySaveAsync),
then(saveCompleted)
);
function applySaveAsync(id) {
// Saving...
}
save(22); // Calling save function with some id.
I can get the id on the validateAsync function, but I cannot return it back since validateAsync should return a promise.
Any way to achieve that?
The simplest choice would be not to use _.flow for the definition of validateAsync.
Since validateAsync does not take parameters nor has a result, you should just change the definition of save to not use _.flow:
function save(id) {
return validateAsync()
.then(function(){ return applySaveAsync(id) })
.then(saveCompleted)
}
We could also change validateAsync to pass through the id:
function validateAsync(id) {
return _(someCondition).showError(ERROR_01).value()
.then(function () { return _(anotherCondition).showError(ERROR_02).value(); })
.then(_.constant(id));
}
and even do that while still using _.flow
var validateAsync = _.flow(
function(id) { return _(someCondition).showError(ERROR_01).value().then(_.constant(id)); },
then(function(id) { return _(anotherCondition).showError(ERROR_02).value().then(_.constant(id)); })
);
but I would advise against that since validateAsync is not supposed to be a function that does takes parameters.
Let's write a wrapper function for such instead to let us do the pass-around in a functional way:
function pass(fn) {
return function(id) {
return fn().then(function() {
return id;
});
}
}
(if you prefer, you can try to compose that from then, _.constant and more)
so that one can write
var save = _.flow(
wrap(validateAsync),
then(applySaveAsync),
then(saveCompleted)
);
I found this package useful for you. In Async cases, you can use this package.
Although flow is one of the best implementations for declarative programming, it doesn't support modern JS programming style.
import { Conductor } from '#puzzleio/conductor';
const conductor = Conductor.createDefault();
const myAsyncWorkflow = conductor
.add(validateAsync)
.if({
check: item => item.isValid === true,
handler: item => console.log('Item is valid')
},
{
// else block
handler: item => console.log('Validation failed')
});
myAsyncWorkflow.run(obj)
.then(() => console.log('Successfully validated'))
.catch(console.error);
I am using Angular resourse to get my data from an API, in this way:
var getAccountListPerUser = function () {
return $resource(uri, {}, {
get: {
headers: service.getDefaultHeaderRequest(),
method: 'GET',
transformResponse: function (data) {
var accountList = [];
try {
accountList = JSON.parse(data);
} catch (e) {
accountList = [];
}
return accountList;
},
isArray: true,
cache: true
}
}).get().$promise;
};
In my controller I have to use it and another two service functions defined in the same way.
var promiseResourcesAccountList = usrWebUserService.getAccountListPerUser();
promiseResourcesAccountList.then(function(result){
$scope.usersWithAccountsAndProfiles = result;
var filteredProfiles = [];
for (var account in result) {
...
}
$scope.filteredProfiles = filteredProfiles;
});
And:
var promiseResourcesEditUser = usrWebUserService.getResourcesUser(currentUser);
promiseResourcesEditUser.then(function (result) {
usrWebUserFactory.mapBasicPreferences($scope, result);
});
And then another very similar, this information loads data in three divs, but I want to show them only when all the three functions have completed correctly. I think I have to chain the result of the promises. How can I do that?
You can chain them like:
promiseResourcesAccountList.then(function(result){
///whatever processing
//return a promise
return promiseResourcesEditUser()
}).then(function(){
return anotherPromise();
}).then(function(){
//update scope here
});
alternatively, you could also use $q.all([promise1, promise2, promise3]).then(...);
#terpinmd is correct. Chaining promises is pretty simple. Say you have a service with a "getWidgets" that returns a promise, and you want to use the response from that service to call another service, "getWidgetOwners" that will return another promise :
Assumptions
getWidgets returns an array of widget objects.
getWidgetOwners accepts an array of ownerIds
How To:
service.getWidgets()
.then(function(widgets) {
return widgets.map(function(widget) { // extract ownerIds
return widget.ownerId;
});
})
.then(service.getWidgetOwners) // pass array of ownerId's to
.then(function(owners) { // the next service
console.log(owners);
});
This is my code:
$scope.updatePosts = function() {
Posts.getIdsFromServer('http://localhost/postIds')
.then(function(ids) {
_.each(ids, function(id) {
Posts.getByIdFromDb(id)
.then(function(p) {
if(p) {
if(p.enabled) {
Posts.getFromServer('http://localhost/post/' + id)
.then(function(post) {
Posts.update(post);
})
}
} else {
Posts.getFromServer('http://localhost/post/' + id)
.then(function(post) {
Posts.insert(post);
})
}
});
});
}, function(error) {
console.log(error);
})
};
This code breaks because of _.each
I searched SO and found this:
function processCoolStuff(coolStuffs) {
return $q.all(_.map(coolStuffs, makeStuffCooler));
}
processCoolStuff(…).then(showAllMyCoolStuff);
But I cannot make it work, becasue I have too many async functions.
And I can get like 10000 posts and 10000 http calls, if I can use above method, will it consume a lot of memory?
How to solve it?
I never had such a problem, but instead of _.each, use angular.forEach
angular.forEach(ids, function(id, index, list) {}
I would like to perform the same action after a promise has either been fulfilled with a success result or failure, ie I want to perform the same action for the success and error handler and then continue to send down the result of the promise to the appropriate erroe/success handlers.
var pleaseWaitPromise = playAudioAsync("please wait");
myLongRunningPromise().then(function tempSuccessHandler(result) {
pleaseWaitPromise.cancel();
return result;
}, function tempErrorHandler(error) {
pleaseWaitPromise.cancel();
return WinJS.Promise.wrapError(error);
}).done(function realSuccessHandler(result) {
console.info(result);
}, function realError(error) {
console.error(error);
});
Is there a more elegant way to stop the pleaseWaitPromise, which could also be a function call instead of a promise (like clearInterval)
jfriend is right you'd typically want finally here - it does exactly what your code above does. Sadly WinJS promises do not feature .finally at the moment so unless you'd like to shim it (patching it on the prototype of WinJS.promise) you're stuck with it.
You can also put it as a function:
function always(prom, fn){
return prom.then(function(v){ fn(v); return v; },
function(e){ fn(e); return WinJS.Promise.wrapError(error); });
}
Which would look like:
always(myLongRunningPromise(),
pleaseWaitPromise.cancel();
})).done(function realSuccessHandler(result) {
console.info(result);
}, function realError(error) {
console.error(error);
});
Sorry, but I don't understand the extra step, wouldn't this just do what you want?
var pleaseWaitPromise = playAudioAsync("please wait");
myLongRunningPromise().then(function tempSuccessHandler(result) {
pleaseWaitPromise.cancel();
console.info(result);
}, function tempErrorHandler(error) {
pleaseWaitPromise.cancel();
console.error(error);
});
edit: second try
I know it is a known anti-pattern, but what if you return a Promise that never fails? Something like:
function neverFails(myLongRunningPromise, pleaseWaitPromise){
return new WinJS.Promise(function (complete){
myLongRunningPromise().then(function () {
pleaseWaitPromise.cancel();
console.info(result);
return complete();
}, function (error) {
pleaseWaitPromise.cancel();
console.error(error);
return complete();
});
});
}
Does that make sense?
I've got the following.
var lookupInit = function () {
http.get('api/employmenttype', null, false)
.done(function (response) {
console.log('loaded: employmenttype');
vm.lookups.allEmploymentTypes(response);
});
http.get('api/actionlist', null, false)
.done(function (response) {
console.log('loaded: actionlist');
vm.lookups.allActionListOptions(response);
});
http.get('api/company', null, false)
.done(function (response) {
console.log('loaded: company');
vm.lookups.allCompanies(response);
});
//... x 5 more
return true;
};
// somewhere else
if (lookupInit(id)) {
vm.userInfo.BusinessUnitID('0');
vm.userInfo.BuildingCode('0');
if (id === undefined) {
console.log('api/adimport: latest');
http.json('api/adimport', { by: "latest" }, false).done(viewInit);
}
else if (id !== undefined) {
console.log('api/adimport: transaction');
http.json('api/adimport', { by: "transaction", TransactionId: id }, false).done(viewInit);
}
} else {
console.log('User info init failed!');
}
The following "http.get('api/employmenttype', null, false)" means i set async to false.
I'm aware that this is probably inefficient. And i'd like to have all the calls load simultaneously.
The only problem is if i don't have them set to async false, the second part of my code might execute before the dropdowns are populated.
I've tried a couple of attempts with Jquery Deferreds, but they have resulted in what i can only describe as an abortion.
The only thing i'm looking to achieve is that the lookup calls finish before the adimport/second part of my code, in any order.... But having each call wait for the one before it to finish EG: async, seems like the only solution I'm capable of implementing decently ATM.
Would this be an appropriate place for deferred function, and could anyone point me into a direction where i could figure out how to implement it correctly, as I've never done this before?
You can use $.when to combine multiple promises to one that resolves when all of them have been fulfilled. If I got you correctly, you want
function lookupInit() {
return $.when(
http.get('api/employmenttype').done(function (response) {
console.log('loaded: employmenttype');
vm.lookups.allEmploymentTypes(response);
}),
http.get('api/actionlist').done(function (response) {
console.log('loaded: actionlist');
vm.lookups.allActionListOptions(response);
}),
http.get('api/company').done(function (response) {
console.log('loaded: company');
vm.lookups.allCompanies(response);
}),
// … some more
);
}
Then somewhere else
lookupInit(id).then(function(/* all responses if you needed them */) {
vm.userInfo.BusinessUnitID('0');
vm.userInfo.BuildingCode('0');
if (id === undefined) {
console.log('api/adimport: latest');
return http.json('api/adimport', {by:"latest"})
} else {
console.log('api/adimport: transaction');
return http.json('api/adimport', {by:"transaction", TransactionId:id});
}
}, function(err) {
console.log('User info init failed!');
}).done(viewInit);
In the Jquery API I've found this about resolving multiple deferreds:
$.when($.ajax("/page1.php"), $.ajax("/page2.php")).done(function(a1, a2){
/* a1 and a2 are arguments resolved for the
page1 and page2 ajax requests, respectively.
each argument is an array with the following
structure: [ data, statusText, jqXHR ] */
var data = a1[0] + a2[0]; /* a1[0] = "Whip", a2[0] = " It" */
if ( /Whip It/.test(data) ) {
alert("We got what we came for!");
}
});
Using this with your code:
var defer = $.when(
$.get('api/employmenttype'),
$.get('api/actionlist'),
$.get('api/company'),
// ... 5 more
);
defer.done(function (arg1, arg2, arg3 /*, ... 5 more*/) {
vm.lookups.allEmploymentTypes(arg1[0]);
vm.lookups.allEmploymentTypes(arg2[0]);
vm.lookups.allEmploymentTypes(arg3[0]);
// .. 5 more
vm.userInfo.BusinessUnitID('0');
vm.userInfo.BuildingCode('0');
if (id === undefined) {
console.log('api/adimport: latest');
http.json('api/adimport', { by: "latest" }, false).done(viewInit);
} else if (id !== undefined) {
console.log('api/adimport: transaction');
http.json('api/adimport', { by: "transaction", TransactionId: id }, false).done(viewInit);
}
});
You can use the defer of the $.when() inside an other $.when(), so if the json calls are not dependant on the first calls you can add them in a an onther defer.