Promises in Ionic 2 / Angular 2, how to? - javascript

I have two functions:
this.geQuizStorage();
this.getQuizData();
geQuizStorage() {
this.quizStorage.getAnswers().then(data => {
return data;
});
}
getQuizData() {
this.quizData.getQuiz().then(data => {
return data;
});
}
I am trying use promises for the 2 functions and wait until both are done, something like:
http.when(this.geQuizStorage(), this.getQuizData()).when(data => {
// data[0] first function response
// data[1]
})
any ideas how to do this in Ionic 2 / Angular 2

You can do this with ES6 promise's all function. No need for external libraries.
Promise.all([this.geQuizStorage(), this.getQuizData()]).then(data => {
//do stuff with data[0], data[1]
});
Your functions should return promises in order for this to work, so I suggest the following modification:
geQuizStorage() {
return this.quizStorage.getAnswers().then(data => {
return data;
});
}
getQuizData() {
return this.quizData.getQuiz().then(data => {
return data;
});
}

Basically you don't need to create another wrapper function for your service call, just to return a data(unless you have your validation logic out there to validate data). Then pass those two function in Observable.forkJoin by passing method promises/observable's & subscribe over that observable to wait till those get complete.
Observable.forkJoin([this.getQuizData(),this.geQuizStorage()])
.subscribe(data => {
console.log(data[0], data[1]);
//both call succeeded
});

Related

How to unit test nested subscribe methods in Angular?

MethodToBeTested() {
this.serviceA.methodA1().subscribe((response) => {
if (response.Success) {
this.serviceA.methodA2().subscribe((res) => {
this.serviceB.methodB1();
})
}
});
}
Here is the scenario.
Things to test:
serviceA.methodA1(). was called.
if response.Success then check if serviceA.methodA2() was called
check if serviceB.methodB1() was called when serviceA.methodA2() received value.
first, one is easy to test.
let spy = spyOn(serviceA, 'methodA1');
expect(spy).toHaveBeenCalled();
But does one test 2 and 3?
let spy= spyOn(serviceA, 'methodA1').and.returnValue({subscribe: () => {success:true}});
subject.MethodToBeTested();
something like that?
Alright, so I figured out what I am looking for is callFake
it('should test inside of subscribe', () => {
let spy = spyOn(serviceA, 'methodA1').and.callFake(() => {
return of({ success: true });
});
let spy2 = spyOn(serviceA, 'methodA2').and.callFake(() => {
return of({ success: true });
});
let spy3 = spyOn(serviceB, 'methodB1').and.returnValue(of({ success: true }));
subject.MethodToBeTested();
expect(spy3).toHaveBeenCalled();
});
I learned that returnValue won't actually execute the inside of the subscribe while callFake will with the data you provide inside it.
It would be better to not use a nested subscribe.
Something like this could be a sollution:
let $obs1 = this.serviceA.methodA1().pipe(share());
let $obs2 = $obs1.pipe(switchMap(x => this.serviceA.methodA2()));
$obs1.subsribe(logic1 here...);
$obs2.subsribe(logic2 here...);

Returning Value from a Function that has Chain Ajax Requests in Jquery

I have a chained Ajax requests in my function.
Function A and B are Ajax requests. A will run first and B will run after A returns its data.
My problem is that Function C is the one that executes Function B. When Function C is executed, a button that executes C will be disabled temporarily until A && B is finished.
How do I return a value on function c below?
I need to return true on the function c AFTER function a & b requests completed returning data.
I'm thinking if I return true on the callback function inside done(), fail() or always(), the value will return to the callback function and not to the c().
function a(methodA) {
return $.ajax({
type: "get",
url: "api/a",
cache:false,
data: { method: methodA },
dataType: "json",
});
}
function b(methodB) {
return $.ajax({
type: "get",
url: "api/b",
cache:false,
data: { method: methodB },
dataType: "json",
});
}
function c(data) {
a(data)
.done(function(data2) {
b(data2)
.done(function(data3) {
})
.fail(function(data3) {
})
.always(function(data3) {
})
})
.fail(function(data3) {
})
}
$(document).ready(function() {
$('#btnC').click(function(event) {
$('#btnC').prop('disabled', true);
var c = c(getData);
if(c == true) {
$('#btnC').prop('disabled', false);
}
});
});
jQuery Ajax method returns a promise object. (To be exact, jqXHR)
Usually, it uses like this.
var result =
$.post("url").done(function() {
// after ajax.
});
// called when ajax process done finished.
result.promise().done(function() {
// blah blah blah
});
However, when it comes to a nested ajax, you need to more work than usual.
Use then() and return inner ajax.
function callAjaxs() {
return $.get("ajaxA").then(function(res) {
return $.get("ajaxB", res).done(function() {
// blah blah blah
});
});
}
// call function and use promise. (of course, you can also use fail, always)
callAjaxs().promise().done(function() {
// $('#btnC').prop('disabled', false);
});
If you don't care about details, I think this way is a good option.
If you want the return value of c to be dependent on asynchronous callbacks you can't just return a value. You have to return a promise because the value you want to return is timed based on the asynchronous requests made by the 2 ajax calls (not available when return is called). Therefor you can promise the return of a value but cannot give the value right away.
function a() {
// mock request since content doesn't matter
return $.get('https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js');
}
function b(fail) {
// google does not allow cross origin requests
if (fail) return $.get('https://google.com');
return a();
}
function c(failB) {
let requests = [
Promise.resolve( a() ),
Promise.resolve( b(failB) ),
];
return Promise
.all(requests)
.then(results => Promise.resolve(true))
.catch(error => Promise.reject('one or multiple requests failed'));
}
c().then(result => {
console.log(result);
}).catch(error => {
console.log(error);
});
c(true).then(result => {
console.log(result);
}).catch(error => {
console.log(error);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Returning just the value true might be a bit useless, since if the promise is resolved you know everything succeeded. You could just simply leave out that part and the results will be passed on as result of the c promise. The same goes for the error message. In the above code I don't use the error itself but instead generate my own string. If you are more interested in the failing object you could leave out this catch in c. See the example below.
function a() {
// mock request since content doesn't matter
return $.get('https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js');
}
function b(fail) {
// google does not allow cross origin requests
if (fail) return $.get('https://google.com');
return a();
}
function c(failB) {
let requests = [
Promise.resolve( a() ),
Promise.resolve( b(failB) ),
];
return Promise.all(requests);
}
c().then(results => {
// in this case has 2 the same values since the same url is requested
results.forEach(resp => console.log(resp.substring(0, 15)));
}).catch(jqXhr => {
console.log(jqXhr.statusText);
});
c(true).then(results => {
results.forEach(resp => console.log(resp.substring(0, 15)));
}).catch(jqXhr => {
console.log(jqXhr.statusText);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Passing arguments while running lodash flow asynchronously

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

Chaining promises in Javascript and Angular

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

How to perform same action regardless of promise fulfilment?

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?

Categories