I have a service in an Angular2 project that takes some parameters and returns a value list to populate drop down menus on a form. When the form component initializes, I need to call the same service multiple times with different parameters to define a number of different dropdown menus, however if I call them all, the last one called clobbers the previous ones, presumably because the subsequent calls are overriding or cancelling the previous fetches.
I've split each of the calls into their own function, but I need a way to call each function in series so that the second doesn't get called until after the first completes. Each function works on its own, however if I call more than one, only the last one succeeds, and the first fail with errors (as the service itself terminates the current fetch when its called with new parameters before finishing).
this.fetchValueListOne();
this.fetchValueListTwo();
this.fetchValueListThree();
I was trying to make this work with promises, but wound up in scoping hell pretty quickly with having to pass the services I wanted to access into the functions and then not being able to get the resulting data back out again - each service call takes three parameters and then sets a specific this.valueList[] variable defined in the component and used on the form.
I also tried creating a list of the functions as variables and then iterating over them, however I ran into the same scoping issues as with promises.
The service returns an Observable, the functions subscribe to that Observable, retrieve the data and assign it to a array variable in the component that a dropdown value list is bound to.
The functions look like this:
fetchValueListOne() {
this.dataSvc.getValueList('Val-List-One', this.stateSvc.currentContext, this.stateSvc.currentLanguageCode)
.map(response => response.json())
.subscribe(
data => {
this.valListOne = data;
},
err => console.log('Error', err),
() => {
console.log('this.valListOne', this.valListOne);
}
);
}
SrAxi pointed me in the right direction, and ultimately I solved the problem as follows where Promises turned out to be the best solution, specifically the Promise / .then mechanism solved the problem nicely.
fetchValueList(listCode): Promise<any> {
return this.dataSvc.getValueList(listCode, this.stateSvc.currentContext, this.stateSvc.currentLanguageCode)
.map(response => response.json())
.toPromise();
}
initializeDropDowns() {
this.fetchValueList('First-Val-List')
.then(data => {
this.firstValList = data;
return this.fetchValueList('Second-Val-List')
}).then(data => {
this.secondValList = data;
return this.fetchValueList('Third-Val-List')
}).then(data => {
this.thirdValList = data;
}) }
I defined the functions in the component, and then called initializeDropDowns() in ngOnInit.
The fetchValueList function returns a Promise, so the first call passes the first listCode and when the Promise resolves, the return value is in the data variable in the .then block where we can assign it to the this.firstValList variable. As the function has returned data, we know the service has finished and it's safe to call again with the second listCode, the return value is in the data variable in the next .then block and we assign it to the this.secondValList variable.
We can chain this as many times as required to populate all the variables, and on the last code block we simply omit the return statement and the block terminates.
This is a very specific use case where we have a single service that needs to be called multiple times as the component initializes, and where the service has to complete its fetch and return a value before it can be called again, but in this case, the Promise / .then method was ideal.
Call the functions when you received the data. Such as:
this.fetchValueListOne().subscribe((firstData) => {
this.fetchValueListTwo(firstData);
// Do something with firstData
}
);
this.fetchValueListTwo().subscribe((secondData) => {
this.fetchValueListThree(secondData);
// Do something with secondData
}
);
this.fetchValueListThree().subscribe((thirdData) => {
// Do something with thirdData
}
);
And declare these functions as Observable, such as:
public fetchValueListOne(): Observable<any> { // Get firstData }
public fetchValueListTwo(): Observable<any> { // Get secondData}
public fetchValueListThree(): Observable<any> { // Get thirdData}
This way you will be certain that when you call a function you have the data from the previous one.
Related
I have the following endpoint in a class called UserApi.js:
const controller = 'User';
...
export async function getEmployeeInfo(employeeId)
{
const query = createQueryFromObject({employeId});
const response = await get(`/${controller}/EmployeeInfo?${query}`);
return retrieveResponseData(response, []);
}
This is going to get the required information from an action method in the backend of UserController.cs.
Now, say that I want to display this information in EmployeeView.vue class, do I have to await it again? Why or why not? Initially, I would say no, you don't, as you already dealt with the await/async in the UserApi.js class, but what about the Promise.resolve? Please explain.
methods: {
async setReportData(
employeeId
) {
this.isBusy = true;
Promise.resolve(getEmployeeInfo(
employeeId
)).then((resultsEmployeeInfo) => {
this.reportDatatableEmployeeInfo = resultsEmployeeInfo;
})
.catch(() => {
this.alerts.error('An error has occurred while fetching the data');
})
.finally(() => {
this.isBusy = false;
});
},
Update:
....
* #param {Object} response
* #param {any} defaultData
* #param {Function} predicate
* #returns {Promise}
*/
export function retrieveResponseData(response, defaultData = null, predicate = (predicateResponse) => predicateResponse) {
const data = predicate(response) ? response.data : null;
return data || defaultData;
}
You need to await it since a function declared with async keyword ALWAYS returns a Promise, even if you do only synchronous stuff inside of it, hence you need to await or "thenize" it to access the value it resolved to. That depends from the implementation details of async functions which are just generators that yield promises.
If this concerns you because you work inside sync modules and don't like to use async functions just to execute more async functions, there's a good news, TOP-LEVEL await MODULES proposal is at stage 4, so it'll very soon be shipped with the next ECMA version. This way you will be able to await inside modules as if they were wrapped by async functions !
https://github.com/tc39/proposal-top-level-await
I can't tell if you need to await it again, because I can't tell what retrieveResponseData does. It might take the resolved value and wrap it in a fresh promise, which would then require callers of getEmployeeInfo to await the result.
Here's the why:
A Promise is a wrapper around a value
await unwraps a Promise. So does the .then() handler you can register with a Promise (but the value is only unwrapped within the function you provide to .then()).
Just like a gift in the real world, once something has been unwrapped, you don't need to unwrap it again. However, very conveniently for us, you can still use await on a value that is not wrapped in a Promise, and it will just give you the value.
You can wrap any value in a Promise, like so:
let wrappedFive = Promise.resolve(5)
//> wrappedFive is a Promise that must be unwrapped to access the 5 inside it
// this does _exactly_ the same thing as the above
let wrappedFive = new Promise(resolve => {
resolve(5)
})
Sometimes you end up in a situation where you can't use await, because you're in a function that cannot be marked async. The lifecycle methods of front-end frameworks like React (and possibly Vue) are like that: the framework needs each lifecycle method to do its job and be done immediately. If you mark the lifecycle method as async, you can often prevent it from having the intended effect.
In cases like that, you often need to use chained .then() handlers, which is a little uglier, but it works:
componentDidMount() {
// this API call is triggered immediately by lifecycle,
// but it basically starts a separate thread -- the rest
// of this function does not wait for the call to finish
API.getUserInfo()
.then(userInfo => {
// this happens after the API call finishes, but
// componentDidMount has already finished, so what happens
// in here cannot affect that function
this.setState({ username: userInfo.username })
})
// this happens immediately after the API call is triggered,
// even if the call takes 30 seconds
return 5
}
Note that a Promise does not actually start a separate thread -- these all happen in the same thread that executes the lifecycle method, i.e. the browser's renderer thread. But if you think of the codepath that executes, a Promise that you don't wait for basically introduces a fork into that codepath: one path is followed immediately, and the other path is followed whenever the Promise resolves. Since browserland is pretty much a single-threaded context, it doesn't really hurt you to think of creating a Promise as spawning a separate thread. This is a nuance you can ignore until you are comfortable with asychronous patterns in JS.
Update: retrieveResponseData does not appear to return a Promise. I could be wrong, if predict returns a Promise, but if that were true, then the ternary would always return response.data because unwrapped Promises are truthy values (even Promise.resolve(false) is truthy).
However, anyone who calls getEmployeeInfo will have to wait it, because that function is marked async, and that means it returns a Promise even if nothing inside it is is asynchronous. Consider this extreme example:
// this function returns a number
function gimmeFive() {
return 5
}
// this function returns a Promise wrapped around a number
async function gimmeFive() {
return 5
}
Async function getEmployeeInfo awaits the result of the get call in order to return the value returned by a call to retrieveResponeData.
Assuming neither get nor retrieveResponeData errors, the value syntactically returned in the body of getEmployeeInfo is used to resolve the promise object actually returned by calling getEmployeeInfo.
Promise.resolve is not needed to convert the result of calling getEmployeeInfo into a promise because, given async functions return promises, it already is.
It doesn't matter if retrieveResponseData returns a promise or not: standard async function processing waits for a returned promise value to be settled before resolving the promise returned when calling the async function.
Async function setRreportData is declared as async but written using chained promise handler methods to process data and error conditions in order - the async declaration could be omitted, but may be useful if modifications are made.
The results can only be used to update the page at a time when the data has finished being obtained and extracted, shown as a comment in the following code:
setReportData( employeeId) {
this.isBusy = true;
getEmployeeInfo(
employeeId
).then((resultsEmployeeInfo) => {
this.reportDatatableEmployeeInfo = resultsEmployeeInfo;
// At this point the result in this.reportDatatableEmployeeInfo can be used to update the page
})
.catch(() => {
this.alerts.error('An error has occurred while fetching the data');
})
.finally(() => {
this.isBusy = false;
});
},
Displaying the data using EmployeeView.vue class must wait until the data is available. The simplest place to insert updating the page (in the posted code) is within the then handler function inside setReportData.
Displaying the data
As posted setReportData does not notify its caller of when report data is available, either by means of a callback or by returning a promise. All it does is save the result in this.reportDatatableEmployeeInfo and dies.
Without using callbacks, setReportData is left with two choices
Return a promise that is fulfilled with, say,resultsEmployeeInfo or undefined if an error occurs:
setReportData( employeeId) {
this.isBusy = true;
// return the final promise:
return getEmployeeInfo(
employeeId
)
.then( (resultsEmployeeInfo) =>
(this.reportDatatableEmployeeInfo = resultsEmployeeInfo)
)
.catch(() => {
this.alerts.error('An error has occurred while fetching the data');
// return undefined
})
.finally(() => {
this.isBusy = false;
});
},
which could be used in a calling sequence using promises similar to
if(!this.busy) {
this.setReportData(someId).then( data => {
if( data) {
// update page
}
}
If you wanted to make the call in an async context you could use await:
if(!this.busy) {
const data = await this.setReportData(someId);
if( data) {
// update page
}
}
Update the page from within setReportData after the data becomes available ( as shown as a comment in the first part of this answer). The method should probably be renamed from setReportData to getReportData or similar to reflect its purpose.
Using Angular and the RxJS library.
I have a simple method:
method(){
const data = this.data;
const moreData = this.moreData;
this.anotherMethodOne(data)
this.anotherMethodTwo(moreData)
}
As you can see I am calling two other methods. anotherMethodOne() should be executed before anotherMethodTwo() - and it is.
Within anotherMethodOne() I have a .subscribe:
anotherMethodOne(data) {
<!-- at this point it jumps out of this method and continues to proceed in the parent method, but returns later -->
this.service.getService(id).subscribe(res => {
res
}
}
As soon as my debugger hits the .subscribe call it jumps back into the parent method() and proceeds to execute this.anotherMethodTwo(moreData). It then jumps back into this.anotherMethodOne(data) to finally complete the .subscribe - however, by this time its too late.
I need to make sure this.anotherMethodOne(data) is completed fully before this.anotherMethodTwo(moreData) runs or ensure that the .subscribe runs before it proceeds any further.
I started looking at switchMap and concat in the RxJS library, but unsure how that would be implemented in my case.
You cannot guarantee an observable would've emitted before proceeding with it's emitted value. So you need to return the observable from the methods and subscribe where it's value is required.
For observable depending on the emissions of other observables, you could use higher order mapping operator like switchMap. More info about them here.
method(){
const data = this.data;
const moreData = this.moreData;
this.anotherMethodOne(data).pipe(
switchMap((someValue) =>
this.anotherMethodTwo(moreData)
)
).subscribe({
// callbacks
});
}
anotherMethodOne(data): Observable<any> {
return this.http.get('url');
}
anotherMethodTwo(moreData): Observable<any> {
return this.http.get('url');
}
When it comes to asynchronous functions, you should stay with the reactive approach and not try to determine when a certain code has finished, but instead just wait for it to finish and then invoke the callback or the responsible handler subsequently. Think about this. What is the subscribe never returns anything - is that okay? What if it takes a really long time to return anything - is that okay too?
But to fix the problem at hand, simply move anotherMethodTwo into the subscription. Then when something is returned, anotherMethodTwo is run.
method(){
const data = this.data;
const moreData = this.moreData;
this.anotherMethodOne(data)
}
anotherMethodOne(data) {
this.service.getService(id).subscribe(res => {
console.log(res)
this.anotherMethodTwo(moreData)
}
}
Going down this path would be similar to async callback hell which nesting style is not recommended but it will do for now.
I've created a code snippet here that fetches data about a certain country using REST Countries API. The function works fine, and returns a pending promise. That promise's fulfilled value will equal an object containing key value pairs for the country's capital, name, and code.
But if I wanted to use them for something, why wouldn't I just set that very same object equal to a variable and continue to program new actions inside my async function? Why bother trying to use values gained asynchronously on the global scope?
function getJSON(url, errorMSG = 'Something went wrong') {
// feed it the fetch URL, then your customized error message
return fetch(url).then(response => {
if (!response.ok) throw new Error(errorMSG);
return response.json();
});
}
async function countryData(nation) {
try {
const info = await getJSON(
`https://restcountries.eu/rest/v2/name/${nation}?fullText=true`,
'Invalid country selected'
);
return {
// Fullfilled value of promise
capital: info[0].capital,
name: info[0].name,
code: info[0].cioc,
};
} catch (err) {
console.error(err); // display your custom error message
}
}
console.log(countryData('Canada'));
fetch is an async function. Why do they resolve the promise to a response object, instead of continuing to run extra actions once they have it? Because they don't know what you want to do with it. It would be impossible for fetch to handle every possible thing that should happen next, so it just has a single job: get the data, then return it (in a promise). You can then combine this with whatever other code you like.
On a smaller scale, this may happen with countryData too. You might have 10 different parts of your app that want to do things with the result from countryData. It may not be "impossible" for countryData to implement all 10 things, but it's definitely impractical and not a good idea. Instead, countryData can be written to have one job: get the country data and return it. Then each of the 10 pieces of code can do their own things with the result.
This isn't about it being async, the same principles apply to synchronous code to. If you can keep code focused on a single task, without entangling it with the needs of other pieces of code, then your code becomes easier to maintain.
I'm trying to get data out of response but I can't seem to get the result I want.
facebookLogin(): void {
this.fb.login()
.then((res: LoginResponse) => {
this.accessToken = res.authResponse.accessToken;
this.expiresIn = res.authResponse.expiresIn;
this.signedRequest = res.authResponse.signedRequest;
this.userId = res.authResponse.userID;
console.log('Logged In', res, this.accessToken); //works without problem
this.router.navigate(['../other-register']);
})
.catch(this.handleError);
console.log(this.accessToken) //printing 'undefined'
}
Here within then => { }, console.log seems to print the data in res without any problem. I can see data I want but when I console.log outside of then =>{ }, it's giving me undefined.
what am I doing wrong? I need to use data inside response and pass them to other component but I can't seem to figure out what I'm doing wrong.
Can anyone help me? Thanks
This is the expected behavior actually.
this.fb.login() is a Promise. This means that the value of the result/response (res) will not readily be available right when it is called, but it 'promises' that it will have a value once some action is taken or a response is returned and 'then' it will do something. In this case that action would be connecting to the Facebook API and having data returned. This is just like Ajax in jQuery if you have experience with that, Promises are a more evolved version of callbacks.
What is happening is that you function is being executed in this order:
this.fb.login() is called. Doesn't have a value yet so it allows the script to continue.
console.log() is called.
this.fb.login's value is returned and the then() closure is executed.
If you want to know when the value is return or perform a specific action once it is returned you can call a function within .then() or look into observables (RxJS) to notify other parts of your application that login was successful (or wasn't).
Observables Example
Here is one example on Observables, however, I would do more research as there are multiple Subjects to select from, all which have slightly different behavior. Also, this kind of pattern works better in Angular2+ if this is performed in a service, that way other components will be able to access the information provided by Facebook.
import { AsyncSubject } from 'rxjs/AsyncSubject';
// ...
response: AsyncSubject<any> = new AsyncSubject();
facebookLogin(): void {
this.fb.login()
.then((res: LoginResponse) => {
this.response.next(res);
this.response.complete();
this.router.navigate(['../other-register']);
})
.catch(this.handleError);
}
You then retrieve the data from within response like this:
this.response.subscribe(result => {
console.log(result);
})
Pass Data Example
Since you already have a function in the service designed to receive the data, this may be a wiser implementation in your case.
facebookLogin(): void {
this.fb.login()
.then((res: LoginResponse) => {
this.user_service.passData(res.authResponse.accessToken);
this.router.navigate(['../other-register']);
})
.catch(this.handleError);
}
I want to implement a dynamic loading of a static resource in AngularJS using Promises. The problem: I have couple components on page which might (or not, depends which are displayed, thus dynamic) need to get a static resource from the server. Once loaded, it can be cached for the whole application life.
I have implemented this mechanism, but I'm new to Angular and Promises, and I want to make sure if this is a right solution \ approach.
var data = null;
var deferredLoadData = null;
function loadDataPromise() {
if (deferredLoadData !== null)
return deferredLoadData.promise;
deferredLoadData = $q.defer();
$http.get("data.json").then(function (res) {
data = res.data;
return deferredLoadData.resolve();
}, function (res) {
return deferredLoadData.reject();
});
return deferredLoadData.promise;
}
So, only one request is made, and all next calls to loadDataPromise() get back the first made promise. It seems to work for request that in the progress or one that already finished some time ago.
But is it a good solution to cache Promises?
Is this the right approach?
Yes. The use of memoisation on functions that return promises a common technique to avoid the repeated execution of asynchronous (and usually expensive) tasks. The promise makes the caching easy because one does not need to distinguish between ongoing and finished operations, they're both represented as (the same) promise for the result value.
Is this the right solution?
No. That global data variable and the resolution with undefined is not how promises are intended to work. Instead, fulfill the promise with the result data! It also makes coding a lot easier:
var dataPromise = null;
function getData() {
if (dataPromise == null)
dataPromise = $http.get("data.json").then(function (res) {
return res.data;
});
return dataPromise;
}
Then, instead of loadDataPromise().then(function() { /* use global */ data }) it is simply getData().then(function(data) { … }).
To further improve the pattern, you might want to hide dataPromise in a closure scope, and notice that you will need a lookup for different promises when getData takes a parameter (like the url).
For this task I created service called defer-cache-service which removes all this boiler plate code. It writted in Typescript, but you can grab compiled js file. Github source code.
Example:
function loadCached() {
return deferCacheService.getDeferred('cacke.key1', function () {
return $http.get("data.json");
});
}
and consume
loadCached().then(function(data) {
//...
});
One important thing to notice that if let's say two or more parts calling the the same loadDataPromise and at the same time, you must add this check
if (defer && defer.promise.$$state.status === 0) {
return defer.promise;
}
otherwise you will be doing duplicate calls to backend.
This design design pattern will cache whatever is returned the first time it runs , and return the cached thing every time it's called again.
const asyncTask = (cache => {
return function(){
// when called first time, put the promise in the "cache" variable
if( !cache ){
cache = new Promise(function(resolve, reject){
setTimeout(() => {
resolve('foo');
}, 2000);
});
}
return cache;
}
})();
asyncTask().then(console.log);
asyncTask().then(console.log);
Explanation:
Simply wrap your function with another self-invoking function which returns a function (your original async function), and the purpose of wrapper function is to provide encapsulating scope for a local variable cache, so that local variable is only accessible within the returned function of the wrapper function and has the exact same value every time asyncTask is called (other than the very first time)