Ember chained promises - javascript

I have a problem that I know is not very challenging. But for some reason, I am having difficulty coming up with a good solution. I need to fetch data from a URL. The URL itself, however, is fetched from another end-point. So, first I fetch an object containing the URL from the server. Upon getting the URL, I access the fetched URL which finally gives me the data I need. The data contains further URLs which I eventually have to fetch. How do I fetch all these data from model hook of ember route?
model(params) {
let futureData = new Ember.RSVP.Promise((resolve,reject) => {
api.ajaxGet(
`${api.buildV3EnterpriseUrl('reports')}` + '/' + params.report_id
).then(function (successVal) {
resolve(successVal);
}, function (error) {
reject(error);
});
});
futureData.then((success) => {
return
api.xmlRequest('GET',success.aggregationUrls.elements[0].url);
})
return Ember.RSVP.hash({
reportData: futureData
});
}

You only forgot one tiny bit:
reportData = futureData.then(…)
Calling then upon the futureData promise doesn't mutate it, it returns a new promise for the result of the callback. You were ignoring that return value.
Then, after also fixing the Promise constructor antipattern, it looks like this:
model(params) {
const futureData = api.ajaxGet(`${api.buildV3EnterpriseUrl('reports')}/${params.report_id}`);
const futureReportData = futureData.then(success => {
return api.xmlRequest('GET',success.aggregationUrls.elements[0].url);
});
return Ember.RSVP.hash({
reportData: futureReportData
});
}
which could also be shortened to
model(params) {
return api.ajaxGet(api.buildV3EnterpriseUrl('reports') + '/' + params.report_id)
.then(success => api.xmlRequest('GET',success.aggregationUrls.elements[0].url))
.then(reportData => ({reportData}));
}

Related

Mapping fetched data

I'm trying to build a TreeNode function in react that builds a tree structure of some data.
I have fake data as a starting point:
const fakeData = {
"1234": {
id: "1234",
name: "Division",
address: "string",
childNodes: []
}
};
When I expand the "Division" node with the "id" 1234, I want to fetch the children of this node. So my fakeData expands to something like this:
I'm using the fetch() method to get data, but I'm unsure of how to pass that data to resolve() method of the Promise. Right now I'm doing this:
function fakeAjax(url, id) {
return new Promise((resolve, reject) => {
fetch(url)
.then(res => res.json())
.then(result => {
const nodes = result.map(node => node.id);
resolve(fakeData[id].childNodes.map(nodes));
});
});
}
function fetchChildNodes(id) {
return fakeAjax(`someUrl`, id);
}
function TreeNode({ id, name }) {
const [childNodes, setChildNodes] = useState(null);
// Toggle our display of child nodes
const toggleExpanded = useCallback(() => {
fetchChildNodes(id)
.then(nodes => setChildNodes(nodes.map(node => <TreeNode {...node} />)))
}
}, [childNodes]);
But this is not passing the relavant data to the resolve method. Am I doing something wrong?
A couple of things jump out:
Your code is using the explicit promise creation anti-pattern. fetch returns a promise, there's no need to wrap new Promise around it. It's not the problem, but it's a problem, not least because the way fakeAjax is written, if the ajax call fails, the promise returned by fakeAjax is never rejected (and never fulfilled, it just stays pending forever).
You're calling map passing in a non-function as the mapping function:
const nodes = result.map(node => node.id);
resolve(fakeData[id].childNodes.map(nodes));
// Here −−−−−−−−−−−−−−−−−−−−−−−−−−−−^
That doesn't make any sense. map accepts a function, not an array.
Your code is getting hit by the footgun in the fetch API that almost everyone else is also hit by: You need to check that the request worked at the HTTP level. fetch only rejects when there's a network error, not an HTTP error (more on my blog here).
I assume your ajax call returns actual node data that you should be passing directly to TreeNode, not modifying (and certainly not turning into an array of just id values).
So something like:
function fetchJSON(url) {
return fetch(url)
.then(res => {
if (!res.ok) {
throw new Error("HTTP error " + res.status);
}
return res.json();
});
}
function fetchChildNodes(id) {
return fetchJSON(`someUrl`, id);
}
The mapping of node data to TreeNode is happening in TreeNode, so I don't think you need to do anything in fetchChildNodes.

Resolve promise when map request are finished

I am working on working on an bookmark page, we get the bookmark results with the bookmarked restaurant id. Then I map the response and push it as objects to an array.
I want to resolve the whole finished array so I can manipulate the data afterwards.
I have created an getData function which does a request to the bookmark api, in the onSucces I call a function called mapResult which looks like this:
mapResults(result: Array<any>, type: string): Promise<any> {
const promise = new Promise<any>((resolve, reject) => {
const requests = result.map((res, i) => {
this.getRestaurantById(res.RestaurantId).then(restaurant => {
const bookmarks = {
createdDate: res.CreatedDate,
restaurant: restaurant[0]
};
this.savedData[type].push(bookmarks);
});
});
Promise.all(requests).then((completed) => {
if(completed) {
console.log(completed)
resolve(this.savedData[type]);
}
})
});
return promise;
}
Where I subscribe to like this:
this.mapResults(result, type).then(data => {
console.log(data)
});
But the data console.log is not the whole data array, it will just resolve the first object.
Why does the Promis.all function not wait for the map to finish?
You have several problems in your code:
You don't return anything in the result.map callback
new Promise is not necessary
You should not use a state variable instead of the promise return value
The completed variable does not make any sense
This code should work as expected:
mapResults(result: Array<any>, type: string): Promise<any> {
// you don't need "new Promise" as "getRestaurantById" already returns a promise itself
const requests = result.map((res) => {
// here you forgot to return something
return this.getRestaurantById(res.RestaurantId).then(restaurant => {
return {
createdDate: res.CreatedDate,
restaurant: restaurant[0]
};
});
});
// using "completed" did not make any sense as it is just an array filled with "undefined"s
return Promise.all(requests).then((restaurants) => {
console.log(restaurants)
// TODO store "restaurants" in "this.savedData[type]" if needed
return restaurants;
});
}
You don't return your promise in the map.

How to return a function as subscription from inside of a promise

I've been returning requests from my functions like this:
makeConnection(data: any) {
console.log('makeConnection');
return this.api.get(data.url, data.dataToSend);
}
So I can subscribe like this: makeConnection.subscribe();
Now I need to check if a variable named urls is set before making an API call So I thought of making a promised function like this
checkIfUrlsPresent():Promise<boolean> {
return new Promise((resolve, reject) => {
if(this.urls) {
resolve(true);
return;
}
this.api.get('/service', null).subscribe((response) => {
console.log(response);
this.urls = response.value.urls;
resolve(true);
});
});
}
And now my service functions look like this:
requestService(data) {
this.checkIfUrlsPresent().then(() => {
console.log('requestService');
let dataToSend = this.api.createFormData(data);
return this.api.post('/service/request/', dataToSend);
})
}
But now I won't be able to subscribe to my function like requestService(data).subscribe()
So my question is how to keep my present flow working while adding this promise or is there any way to check that my variable is set before making a call while keeping this flow working.
Note: The problem is that I'm making all my api calls from one files and there are too many functions that calls these APIs so I need this way so that I won't need to make changes in all my pages to check if url is present.
Almost there. need to return the promise inside the function as well
requestService(data) {
return this.checkIfUrlsPresent().then(() => { // return the promise
console.log('requestService');
let dataToSend = this.api.createFormData(data);
return this.api.post('/service/request/', dataToSend);
})
}
Have requestService return the chained promises, and call .then on it:
requestService(data) {
return this.checkIfUrlsPresent().then(() => {
console.log('requestService');
let dataToSend = this.api.createFormData(data);
return this.api.post('/service/request/', dataToSend);
})
}
// ..
instance.requestService(someData)
.then(service => service.subscribe())

Nesting a http request inside list of promises

I have a service function that returns a list of promises:
getData(user) {
return this.$q.all({
userInfo: this.$http.get(this.buildUrl('user.getinfo', user)),
userTopArtists: this.$http.get(this.buildUrl('user.gettopartists', user)),
userChart: this.$http.get(this.buildUrl('user.getWeeklyChartList', user))
}).then(resp => {
return resp;
}).catch(err => {
this.$q.reject('Error' + err.status);
})
}
, which I'm calling inside my controller:
validateUser() {
this.error = null;
this.service.getData(this.$scope.username)
.then(resp => {
if (resp.userInfo.data.user) {
this.service.storeUserData('userData', JSON.stringify(resp));
this.$location.path('/profile');
} else {
this.error = resp.userInfo.data.message;
}
})
}
Works fine until now but what I'm looking for is manipulating what data I get from the userChart request in particular.
I want to manipulate the json I get from calling userChart, store some of it inside of an array and make another request that returns data using the stored array object values from the initial request as parameters.
So basically I don't need the json from userChart, I just need to use to it make a nested(?) request using some of its data.
If you return a promise from the then the caller of the original promise will wait until the nested promise is resolved. It does not matter if the caller was your service using $q.all or something else, it is chained.
This shows just the relevant code, it located in your service with everything else being unchanged.
userChart: this.$http.get(this.buildUrl('user.getWeeklyChartList', user))
.then((result) => {
// additional manipulation if needed on result
// second call to http and return the resulting promise
return this.$http.doSomething('');
});
I haven't tried this, but maybe you could do something with the result of that call as soon as it comes back? Something like this?
getData(user) {
return this.$q.all({
userInfo: this.$http.get(this.buildUrl('user.getinfo', user)),
userTopArtists: this.$http.get(this.buildUrl('user.gettopartists', user)),
userChart: this.$http.get(this.buildUrl('user.getWeeklyChartList', user)).
then(function(response) {
// Do something with the successful response
}, function(response) {
// Do something for the failed response
})
}).then(resp => {
return resp;
}).catch(err => {
this.$q.reject('Error' + err.status);
})
}
If I understood yours needs you should first get the charts response and then use that response to call the other webservices, so something like this should work:
validateUser() {
this.getData({})
.then(function (response) {
// response containing 'userInfo' and 'userTopArtists'
console.log(arguments);
});
}
getData(user) {
const me = this;
return me.$http.get('https://jsonplaceholder.typicode.com/users')
.then(function (charts) {
return me.$q.all({
// use charts as param for other calls...
userInfo: me.$http.get('https://jsonplaceholder.typicode.com/users'),
userTopArtists: me.$http.get('https://jsonplaceholder.typicode.com/users')
});
})
.catch(err => {
me.$q.reject('Error' + err.status);
});
}

Typescript: how to structure a fetch API call inside a method that returns a Promise response

Maybe a trivial one, but I am new with Typescript and fetch API.
In an exported class I have a public method remoteFetchSomething like:
export class className {
remoteFetchSomething = (url : string) : Promise<Response> => {
return fetch(url)
.then(
(r) => r.json()
)
.catch((e) => {
console.log("API errore fetching " + objectType);
});
}
}
export const classInstance = new className();
The method queries a remote JSON API service, and in the code, I am using it like:
import { classInstance } from ...
classInstance.remoteFetchSomething('https://example.url')
.then((json) => {
console.log(json);
}
)
The console.log is actually showing the results, but the remoteFetchSomething returns a Promise and I am unable to parse and access the JSON object values.
I would like to wait for the response before executing the remaining code, but how do I unwrap content from promise? Should I again put another .then? What am I missing?
Thank you.
By now I resolved the problem defining the return type of the remoteFetch as any:
remoteFetchSomething = (url : string) : any => {
return fetch(url)
.then(
(r) => r.json()
)
.catch((e) => {
console.log("API errore fetching " + objectType);
});
}
And now I can access JSON values like data below:
classInstance.remoteFetchSomething('https://example.url').then(
(json) => {
console.dump(json.data);
}
)
[sincerely still not clear why I cant' use the Promise<Response> type]
You can't synchronously block while waiting for a request in javascript, it would lock up the user's interface!
In regular javascript, and most versions of TypeScript, you should be / must be returning a promise.
function doRequestNormal(): Promise<any> {
return fetch(...).then(...);
}
function someOtherMethodNormal() {
// do some code here
doRequestNormal.then(() => {
// continue your execution here after the request
});
}
In newer versions of typescript, there's async/await keyword support - so instead it might look like this:
async function doRequestAsync() {
var result = await fetch(...);
// do something with request;
return result;
}
async function someOtherMethodAsync() {
// do some code here
var json = await doRequestAsync();
// continue some code here
}
Keep in mind, doRequestAsync still returns a Promise under the hood - but when you call it, you can use await to pretend that you're blocking on it instead of needing to use the .then( callback. If you call an async method from a non-async method, you'll still need to use the callbacks as normal.
this is how I do it:
type Payload = {
id: number
}
type ReturnType = number
export const functionThatHasNumberType = async (
payload: Payload
): Promise<ReturnType> => {
let url = `/api/${payload.id}`
return await axios.get(url)
}

Categories

Resources