I'm doing a lot of promise based operations in an Express/Sequelize app. In order to control the data flow, I want to follow the promise model as closely as possible.
Here's a snippet of what I'm currently doing:
AreaList.forEach( area => {
distances.push({
target: tgt,
source: area,
distance: areaController.calcDist(tgt, area),
country: areaController.determineCountry(area)
}).then(() => { //this is where I would like to have the .then() function, but get the error.
distances.sort((a1, a2) =>{
return a1.distance - a2.distance;
}).filter(area => area.country === country)
.forEach(area => {
Insider.findAll({
where: {
areaCode: area.source,
country: area.country,
language: language,
gender: gender
}
}).then( insider => {
returnUsers.push(insiders);
}).then(_ => {
returnUsers = returnUsers.splice(0,10);
res.status(200).send(returnUsers);
});
});
});
});
How can I either provide the .then() for the Array, or simulate the .then()?
The problem you are having is mixing synchronous code with asynchronous code. In your code snippet above you have various pieces of synchronous code. AreaList.forEach, distances.push, distances.sort are all synchronous operations.
What it seems to me you are trying to do is process some code while pushing it into an array, this may or may not be asynchronous (areaController.calcDist(tgt, area)).
I would rewrite something like this assuming areaController.calcDist(tgt, area) is a synchronous operation:
let distances = AreaList.map(area => {
return {
target: tgt,
source: area,
distance: areaController.calcDist(tgt, area),
country: areaController.determineCountry(area)
};
})
.sort((a1, a2) =>{
return a1.distance - a2.distance;
})
.filter(area => area.country === country);
let findUsers = distances.map(area => {
return Insider.findAll({
where: {
areaCode: area.source,
country: area.country,
language: language,
gender: gender
}
});
});
Promise.all(findUsers).then(users => {
let returnUsers = users.splice(0, 10);
res.status(200).send(returnUsers);
})
.catch(err => {
//handle errors
});
then() is a function provided by javascript promises resolve. if you want to do a sequence of stuff when going through each item in your AreaList, you should do this:
pushDistances(distance) {
return new Promise((resolve) => {
distance.push(distance);
resolve();
});
with the resolve() function, you can either just resolve() it without anything or attach something that you'd like to resolve with: like the distance you just pushed or the updated distances array with the newly pushed distance.
resolve(distances) or resolve(distance);
and then in your .then(), you get either distances or distance, based on what you resolved with. like this:
pushDistances().then((distance or distances) => {
within this function you have access to distance or distances: based on what you resolved with.
});
and then you can just chain a bunch of functions that return promises
pushDistances().then(() => anotherFunctionThatReturnsPromise()).then(() => {})
that's a general overview of how promises work. You should look more into
Javascript Promises to see how you can chain promises.
Related
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 3 years ago.
Im geocoding my address, from something like "Address 150, City" to it's latitude and longitude using Google Geocoding API. At the end of a function, im trying to return an object, which contains these two as shown in code below.
I've thought that the problem may be, that the coding takes quite some time, and since I wanted to resolve this problem today and I haven't learned await and async with promises and stuff(i will do that tommorow, dont worry), I used setTimeout instead, but it didnt solve the problem even thought I was calling console.log after the coding was done. Since I'm new to JS, I then just tried to wrap it into variable, just play with the code a little, but neither did help.
// address
var address = "Address 156, City"
function geocode(address) {
axios.get("https://maps.googleapis.com/maps/api/geocode/json", {
params: {
address: address,
key: "AIzaSyDaXOpbCeLYeRxWXtuSQQEbQzR14PejczM"
}
})
.then((response) => {
let lngLat = {
lat: response.data.results[0].geometry.location.lat,
lng: response.data.results[0].geometry.location.lng}
console.log(lngLat) // here it shows me my object
return lngLat;
})
}
let geocodedAddress = geocode(address);
setTimeout(function () { console.log(geocodedAddress); }, 3000);
}
});
You have no return in your function. The return in then() does not return to the outer function.
In addition, the request is asynchronous so you need to return the axios.get promise and use another then() to access the data when function is called
function geocode(address) {
// return the promise
return axios.get("https://maps.googleapis.com/maps/api/geocode/json", {
params: {
address: address,
key: "AIzaSyDaXOpbCeLYeRxWXtuSQQEbQzR14PejczM"
}
})
.then((response) => {
let lngLat = {
lat: response.data.results[0].geometry.location.lat,
lng: response.data.results[0].geometry.location.lng
};
return lngLat;
});
}
// usage
geocode(address).then(lngLat => {
console.log(lngLat);
});
Well, I don't have access to your entire code, but an async/await approach to solve your issue you would be:
// address
var address = "Address 156, City"
async function geocode(address) {
const response = await axios.get("https://maps.googleapis.com/maps/api/geocode/json", {
params: {
address: address,
key: "AIzaSyDaXOpbCeLYeRxWXtuSQQEbQzR14PejczM"
}
})
let lngLat = {
lat: response.data.results[0].geometry.location.lat,
lng: response.data.results[0].geometry.location.lng
}
console.log(lngLat) // here it shows me my object
return lngLat;
}
let geocodedAddress = await geocode(address);
console.log(geocodedAddress);
}
});
Note how async/await allows you to write simple sequential logic to perform asynchronous operations.
I have been pounding my head against this problem, and need help with a solution. I have an array of IDs within a JSON, and I am iterating over that array and making a GET request of each ID. I want to then push the response of those GET requests into an array.
Here is the function I am using to push the registrations into the array. It is iterating through an array of IDs:
getRegistrations = async (event) => {
let registrations = [];
await event.registrations.forEach(registration => axios.get('/event/getRegistration', {
params: {
id: registration
}
}).then(res => {
registrations.push(res.data.properties)
}
).catch(err => console.log(err)));
return registrations;
};
Here is where I am calling that code:
render() {
let event = this.getEvent();
let registrations2 = [{
age: 19,
bio: 'test',
firstName: 'hello',
lastName: 'bye',
password: 'adadas',
telephone: "4920210213"
}];
if (this.props.listOfEvents.events.length !== 0 && !this.props.listOfEvents.gettingList && event) { //check if the array is empty and list has not been rendered yet
let columns = [];
let registrations = this.getRegistrations(event);
console.log(registrations);
let eventProperties = event.properties[0];
Object.keys(eventProperties).forEach(key => columns.push({
title: eventProperties[key].title,
dataIndex: key,
key: key
}));
console.log(registrations);
console.log(registrations2);
return (
<h1>hi</h1>
)
}
return <Loading/>
}
When I console-log 'registrations' vs 'registrations2' they should be very identical. However, in the javascript console on Google Chrome, 'registrations appears as '[]' where 'registrations2' appears as '[{...}]'.
I know that it is an issue related to promises (I am returning the registrations array before actually pushing) but I have no idea how to fix it! Some friendly help would be very much appreciated!
I recommend Promise.all, it will resolve single Promise after all promises have resolved. And technically async function is also promise so it will return promise.
here the example.
https://codesandbox.io/s/jzz1ko5l73?fontsize=14
You need to use componentDidMount()lifecycle method for proper execution and state to store the data.
constructor (props) {
super(props);
this.state = {registrations :[]}
}
componentDidMount () {
let response = this.getRegistrations()
this.setState({registrations : response});
}
Then access that state in render method. It's not good practice to call api from render mothod.
Since getRegistrations(event) returns a promise, you should perform operations on its return value inside then.
Instead of
let registrations = this.getRegistrations(event);
console.log(registrations);
Do this
this.getRegistrations(event).then(registrations => {
console.log(registrations);
// other operations on registrations
});
I used to have the following code:
function makeCall(userInfo) {
api.postUser(userInfo).then(response => {
utils.redirect(response.url);
})
// other logic
return somethingElse;
}
And I was able to write a test that looked like this:
const successPromise = Promise.resolve({ url: 'successUrl' })
beforeEach(function() {
sinon.stub(api.postUser).returns(successPromise);
}
afterEach(function() {
api.postUser.restore();
}
it "calls API properly and redirects" do
makeCall({});
expect(api.postUser).calledWith(userInfo).toBe(true);
successPromise.then(() => {
expect(utils.redirect.calledWith('successUrl')).toBe(true);
done();
}
emd
And everything was green.
Now, I had to add another promise to make another external call, before doing the api postUser call, so my code looks like this:
function makeCall(names) {
fetchUserData(names).then(userData => {
return api.postUser(userData).then(response => {
utils.redirect(response.url);
})
})
// other logic
return somethingElse;
}
where fetchUseData is a chain of many promises, such like:
function fetchNames(names) {
// some name regions
return Promise.all(names);
}
function fetchUserData(names) {
fetchUsersByNames(names).then(users => {
// For now we just choose first user
{
id: users[0].id,
name: users[0].name,
}
});
}
And the tests I had fail. I am trying to understand how to change my tests to make sure that I am still testing that I do the final API call properly and the redirect is also done. I want to stub what fetchUserData(names), to prevent doing that HTTP call.
You're not using promises correctly. Your code doesn't have a single return statement, when it should have several (or it should be using arrow functions in such a way that you don't need them, which you're not doing).
Fix your code:
function makeCall(names) {
// v---- return
return fetchUserData(names).then(userData => {
// v---- return
return api.postUser(userData).then(response => {
utils.redirect(response.url);
})
})
}
function fetchUserData(names) {
// v---- return
return fetchUsersByNames(names).then(users => {
// For now we just choose first user
// v---- return
return {
id: users[0].id,
name: users[0].name,
}
});
}
Once you've done that, you can have your test wait for all of the operations to finish.
Test code:
makeCall(['name']).then(() =>
expect(api.postUser).calledWith(userInfo).toBe(true);
expect(utils.redirect.calledWith('successUrl')).toBe(true);
done();
});
You should add a return statement, otherwise you are not returning promises nowhere:
function fetchNames(names) {
// some name regions
return Promise.all(names);
}
function fetchUserData(names) {
return fetchUsersByNames(names).then(users => {
// For now we just choose first user
{
id: users[0].id,
name: users[0].name,
}
});
}
So when you are using Promise.all(), then you will have as result of the promise an array with all the value returned by all the promises.
So then this method should look like this when called:
fetchNames(names).then((arrayOfResolvedPromises) => {
// here you will have all your promised resolved and the array holds all the results
});
So inside your test you can move your done inside the block where all the promises will be resolved.
In addition, I strongly suggest you to use a library as chai-as-promised for testing promises.
It has a lot of nice methods for testing your promises.
https://github.com/domenic/chai-as-promised
I have an array of promises, and I'm trying to push new promises into that array inside of another dispatch.then function, but it appears that the array is always out of scope
load(params, auth) {
return dispatch => {
const { passage, versions, language_tag } = params
let promises = []
versions.forEach((id) => {
// get the version info, and then pass it along
dispatch(ActionCreators.version({ id: id })).bind(promises).then((version) => {
promises.push(dispatch(ActionCreators.passages({
id: id,
references: [passage],
versionInfo: {
local_abbreviation: version.abbreviation,
local_title: version.title,
id: version.id,
},
})))
})
})
//
promises.push(dispatch(ActionCreators.configuration()))
promises.push(dispatch(ActionCreators.byRef({ language_tag })))
console.log(promises.length)
return Promise.all(promises)
}
},
I've tried a few different approaches, such as setting var that = this right before the dispatch inside of the versions loop, and what is shown here, trying to use .bind(promises) on the dispatch.
promises.length is always 2, (because of the two that are actually getting pushed at the bottom). I can console statements inside of the .then so I know it's getting executed, but the dispatches are not ending up in the promises array.
I could very well be thinking of the dispatch function in an incorrect way.
Any help would be appreciated!
The problem is that since you're adding the promises on then(), you have already returned the array by the time you're adding the promises. So they do get added, but too late.
Instead, try this:
load(params, auth) {
return dispatch => {
const { passage, versions, language_tag } = params;
let promises = [];
versions.forEach((id) => {
// get the version info, and then pass it along
promises.push(dispatch(ActionCreators.version({ id: id })).then((version) => {
return dispatch(ActionCreators.passages({
id: id,
references: [passage],
versionInfo: {
local_abbreviation: version.abbreviation,
local_title: version.title,
id: version.id,
},
}));
}));
});
//
promises.push(dispatch(ActionCreators.configuration()));
promises.push(dispatch(ActionCreators.byRef({ language_tag })));
console.log(promises.length);
return Promise.all(promises)
}
}
I want to do a fetch then update the markers state. The problem is the state is updating before my fetch ends (due to asynchronous I think). I think there would be a solution with UnderscoreJS using the _.after function to set the state after my fetch ends. I don't know how to do that. Any idea?
Here my code:
onRegionChange(region) {
let latitude = region.latitude;
let longitude = region.longitude;
let markers = [];
_.filter(this.props.stations, (v) =>
{
if (this.getDistanceFromLatLonInKm(latitude,longitude,v.position.lat,v.position.lng) < 1) {
fetch('https://api.jcdecaux.com/vls/v1/stations/' + v.number + '?contract=Paris&apiKey=3a9169028401f05f02bcffd87f4a3963dcd52f63')
.then((response) => response.json())
.then((station) => {
console.log("station", station);
markers.push({
number: station.number,
coordinate: {
latitude: station.position.lat,
longitude: station.position.lng
},
title: station.name,
description: station.address,
banking: station.banking,
bonus: station.bonus,
status: station.status,
bike_stands: station.bike_stands,
available_bike_stands: station.available_bike_stands,
available_bikes: station.available_bikes,
last_update: station.last_update
});
})
.catch((error) => {
console.warn(error);
});
}
}
)
this.setState({
markers: markers
})
console.log("markers", this.state.markers);
}
Your mixing synchronous and asynchronous ideas and they don't mix well.
You're better off converting the synchronous values into promises (same thing the fetch is doing).
First I break out the drivel parts into functions. This cleans up the actual code and lowers the complexity.
function stationFetchUrl(station) {
return `https://api.jcdecaux.com/vls/v1/stations/${station.number}?contract=Paris&apiKey=3a9169028401f05f02bcffd87f4a3963dcd52f63`;
}
function convertStationData(station) {
return {
number: station.number,
coordinate: {
latitude: station.position.lat,
longitude: station.position.lng
},
title: station.name,
description: station.address,
banking: station.banking,
bonus: station.bonus,
status: station.status,
bike_stands: station.bike_stands,
available_bike_stands: station.available_bike_stands,
available_bikes: station.available_bikes,
last_update: station.last_update
};
}
function stationNearRegion(region, station) {
const distance = this.getDistanceFromLatLonInKm(
region.latitude,
region.longitude,
stationStub.position.lat,
stationStub.position.lng
);
return (distance < 1);
}
Next in the event handler I map over the stations and convert them to either null (in the case of the distance being >= 1) or fetch the new station data from the server (fetch()).
This results in an array of either promises or nulls. I use Underscore's .compact() to remove the null (same as filter did). Then I have an array of promises which I pass to Promise.all which waits till all the promises in the array resolve. When they do it passes the result of those promises (as an Array) to the .then() function which I dutifully use this.setState() on.
onRegionChange(region) {
const markerPromises = _.chain(this.props.stations)
.map(stationStub => {
if (stationNearRegion.call(this, region, stationStub) {
return fetch(stationFetchUrl(station))
.then(response => response.json())
.then(convertStationData);
}
})
.compact()
.value();
Promise.all(markerPromises)
.then(markers => this.setState({markers}))
.catch(error => console.log(error.toString()));
});
Since the onRegionChange is an event handler it would not return a promise. Instead we handle a possible error(s) by logging them in the .catch() block.
References
Chaining in Underscore.js (miniarray.com)
Promise - JavaScript | MDN (developer.mozilla.org)
You're Missing the Point of Promises (blog.domenic.me)