I've been stuck working on this exercise FOR AGES, I'm finally throwing in the towel and asking for some help.
Make an AJAX call to the Star Wars API [https://swapi.co/] and get the opening crawl for each film in the series. Once you have finished that, loop through the array of planets for each movie and make more AJAX calls to collect the name of each planet, organized by film. Then, console log an array of objects in which each object contains the opening crawl for a specific movie, along with the names of every planet featured in that movie.
I've read a few articles and watched a few videos on Asynchronous js, and I 'think?', I sorta get it. This really doesn't want to work though.
var promiseList = [];
var fetchArray = function(arr) {
promiseArr = arr
.map(url => fetch(url)
.then(res => res.json())
.then(planet => planet.name)
);
Promise.all(promiseArr)
.then(data => console.log(data));
}
// fetchArray doesn't work at all. Curious if it's possible to run this code as it's technicall synchronous (idk).
for (let number = 1; number < 8; number ++) {
var t = fetch(`https://swapi.co/api/films/${number}/`)
.then(res => res.json())
promiseList.push(t)
}
console.log("NAHANH", promiseList)
// Prints out [[object Promise] { ... }, [object Promise] { ... }, [object Promise] { ... }, [object Promise] { ... },
[object Promise] { ... }, [object Promise] { ... }, [object Promise] { ... }]
Promise.all(promiseList)
.then(films => films.map(film => ({
"title": film.title,
"planets": film.planets,
"opening_crawl": film.opening_crawl,
})))
// Works up untill this point, this next then doesn't work, my aim is to alter the film.plants property in every
array object and actually fetch the planet rather than just the url!
// An example print out would be...
// {
// opening_crawl: "Luke Skywalker has vanis...",
// planets: ["https://swapi.co/api/planets/61/"],
// title: "The Force Awakens"
// }]
.then(films => films.map(film => {
film.planets = film.planets
.map(url => fetch(url)
.then(res => res.json())
.then(planet => planet.name)
.catch(error => console.log(error.message))
);
}
.then(data => console.log(data))
// Which would then finally resolve to (this is what I want but cannot get at the moment!)
// {
// opening_crawl: "Luke Skywalker has vanis...",
// planets: ["Yavin IV"],
// title: "The Force Awakens"
// }]
It almost works I can return an object. The fetch arrays doesn't work at all. and my second alteration attempting to retrieve the planet name doesn't work.
First, rename function fetchArray as fetchPlanetNames, as that's what it does, and add a couple of missing returns. Then you have a worker function that makes the main code block much simpler ( as was your intention :-) ).
In the main code block, fetchPlanetNames(...) returns Promise, not Array, therefore, inside the films.map() functor, you need fetchPlanetNames(film.planets).then(planets => {/* compose the required film object here */}). It's a kind of "inside-out" version of what you tried.
var fetchPlanetNames = function(arr) {
return Promise.all(arr.map(url => { // return the Promise returned by Promise.all(...).then(...)
// ^^^^^^
return fetch(url)
.then(res => res.json())
.then(planet => planet.name);
}))
.then(data => {
console.log(data);
return data; // Remember to return `data`, otherwise undefined will be dlivered.
// ^^^^^^^^^^^^
});
};
var promiseList = [];
for (let number = 1; number < 8; number ++) {
promiseList.push(fetch(`https://swapi.co/api/films/${number}/`).then(res => res.json()));
}
Promise.all(promiseList)
.then(films => {
return Promise.all(films.map(film => {
// Here, a nested Promise chain allows `film` to remain in scope at the point where 'planets' become available.
return fetchPlanetNames(film.planets)
.then(planets => {
return {
'title': film.title,
'planets': planets,
'opening_crawl': film.opening_crawl
};
});
}));
})
.then(data => console.log(data));
Related
I'm working with some old code and I'm trying to return results for two arrays of promises.
So basically body contains first and second which are arrays of ids. I know that the below code works for a small array of of 1 to 5 ids, but if I were to do a few hundred, would the resolve fire before I get the hundred of results from both promise arrays?
const doSomething = (body) => {
return new Promise((resolve, reject) => {
const promisesOne = body.first.map((id) =>
doSomethingOne(id)
);
const promisesTwo = body.second.map((id) =>
doSomethingTwo(id)
);
let response = {
first: {}
second: {}
headers: 'mock headers'
};
Promise.all(promisesOne).then((results) => {
response.first = results;
});
Promise.all(promisesTwo).then((results) => {
response.second = results;
});
resolve(response);
});
};
Also I won't be able to refactor this to async/await as this codebase does not use it.
would the resolve fire before I get the ... results from both promise arrays?
Yes, absolutely because you aren't waiting for them to resolve.
Simply wrap the two Promise.all() promises in another Promise.all() to wait for everything then create the final object you want.
const doSomething = (body) => {
return Promise.all([
Promise.all(body.first.map(doSomethingOne)),
Promise.all(body.second.map(doSomethingTwo)),
]).then(([first, second]) => ({
first,
second,
headers: "mock headers",
}));
};
You could make this generic by creating a map of body property names to their corresponding mapping function. For example
const mappers = {
first: doSomethingOne,
second: doSomethingTwo,
};
const doSomething = (body) => {
return Promise.all(
Object.entries(mappers).map(([key, mapper]) =>
Promise.all(body[key].map(mapper)).then((data) => ({ [key]: data }))
)
)
.then((results) => Object.assign({ headers: "mock headers" }, ...results));
};
Your code will not work at the moment - the resolve(response) will run before either of the Promise.alls are done.
First, refactor your .first and .second (etc) properties so that they're arrays, rather than unique string properties. Then, it'll be easy to .map them with two Promise.alls - one to wait for an individual array to fulfill, and an outer one to wait for all arrays to fulfill.
The same thing should be done for the doSomethingOne etc functions - to make the code easy to work with, use an array of functions instead of having many standalone identifiers.
Also, there's no need for the explicit Promise construction antipattern.
const doSomethings = [
doSomethingOne, // define these functions here
doSomethingTwo,
];
const doSomething = (body) => Promise.all(
body.arrayOfArraysOfIds.map(
(arr, i) => Promise.all(
arr.map(id => doSomethings[i](id))
)
)
)
.then((results) => ({
headers: 'mock headers',
results,
});
I'm trying to learn exactly how to oversee the order in which my script runs using asynchronous functions and promises. At this point I've read/watched many information resources to try to get this right but something about my code just isn't working:
I have a generic vue.js object containing my methods, and running the series of functions asynchronously when the app is mounted:
var testAPP = new Vue({
el: '#test-app',
data: {
},
methods: {
fake_fetch: function () {
return new Promise((resolve) => {
setTimeout(() => {console.log("Returning fetch results."); resolve("good boy!")}, 10000)
})
},
first_step: function () {
console.log('Playing with my puppy!')
this.fake_fetch()
.then((data) => {
let praise = "Buddy is a " + data
console.log(praise)
return ("nap.")
})
.then((data) => {
return new Promise((resolve) => {
setTimeout(() => {console.log("Phew! We are taking a " + data); resolve(true)}, 20000)
})
})
},
second_step: function () {
console.log('Baking some cookies!')
this.fake_fetch()
.then((data) => {
data = data.replace(" ", ", ")
let praise = "Oh man these are " + data
console.log(praise)
return ("nap.")
})
.then((data) => {
return new Promise((resolve) => {
setTimeout(() => {console.log("I'm so full, I'm taking a " + data); resolve(true)}, 20000)
})
})
},
third_step: function () {
console.log('Putting out a hit on Polly')
this.fake_fetch()
.then((data) => {
let praise = "The job was a success? You did " + data
console.log(praise)
return ("nap.")
})
.then((data) => {
return new Promise((resolve) => {
setTimeout(() => {console.log("A moment of silence for Polly as he takes a dirt " + data); resolve(true)}, 20000)
})
})
},
get_started: function () {
v = this
async function my_day(v) {
const task_one = await v.first_step()
const task_two = await v.second_step()
const task_three = await v.third_step()
return ([task_one, task_two, task_three])
}
my_day(v).then((data) => {
if (false in data) {
return ("Something went wrong.")
} else {
return ("My day is done, time to rest... again.")
}
})
},
},
mounted: function () {
this.get_started()
}
})
The result I expect to get based on the order I 'thought' would be correct is this:
Playing with my puppy!
Returning fetch results.
Buddy is a good boy!
Phew! We are taking a nap.
Baking some cookies!
Returning fetch results.
Oh man these are good, boy!
I'm so full, I'm taking a nap.
Putting out a hit on Polly
Returning fetch results.
The job was a success? You did good boy!
A moment of silence for Polly as he takes a dirt nap.
My day is done, time to rest... again.
Ignoring the silliness of the output statements, thats the order they should appear. My current code gives me this:
Playing with my puppy!
Baking some cookies!
Putting out a hit on Polly
My day is done, time to rest... again.
Returning fetch results.
Buddy is a good boy!
Returning fetch results.
Oh man these are good, boy!
Returning fetch results.
The job was a success? You did good boy!
Phew! We are taking a nap.
I'm so full, I'm taking a nap.
A moment of silence for Polly as he takes a dirt nap.
The first four lines come up right away which doesn't feel right as there should be my built in delays stalling each 'step' function before the next one fires.
I've tried many things from making 'my_day()' its own function within methods and calling it via this.my_day() but that didn't make a difference. I've tried playing around with different return statements on the promises as well as making each following step function dependent on the variable of its predecessor but these resulted in the same functionality.
Why are my functions starting at the same time and not following the 'order' I have built into my async function?
Thank you for any help!
Also the html I used to run this in Edge is simple, I was just interested in the console:
<!DOCTYPE html>
<script src="https://unpkg.com/vue#2.6.10/dist/vue.min.js"></script>
<script type="text/javascript" src="C:\Users\adarwoo\Documents\Python Scripts\JS\async2.js"></script>
You are using await, but there is no async
first_step: async function() { // <-- async needed
console.log('Playing with my puppy!')
return this.fake_fetch() // <-- return needed
.then((data) => {
let praise = "Buddy is a " + data
console.log(praise)
return ("nap.")
})
.then((data) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log("Phew! We are taking a " + data);
resolve(true)
}, 20000)
})
})
}
I want to .map instead of for loop but I still confuse how to use it here is my data
const arr = [
{
name: "John",
subject : [
{
subject_id : 1,
score :20
}
]
},
{
name: "Doe",
subject : [
{
subject_id : 2,
score :20
}
]
}
]
I want to add subject detail into subject array
async getData() {
for (let i in arr) {
let data = arr[i];
for (let i in data.subject) {
const subject = data.subject[i];
//this line code will get subject detail
const getSubject = await this.getSubject(subject);
subject["subjectData"] = getSubject;
}
}
}
everything work fine but How can I use map instead of for loop
example for output
{
name: "Doe",
subject : [
{
subject_id : 2,
score :20,
subjectData: {
//some detail
}
}
]
}
Here is what I want to do
const newData = arr.map(async data => {
const getSubject = data.subject;
const subject = getSubject.map(async zp02 => {
const getSubject = await this.getSubject(subject);
return { getSubject };
});
return { ...data, subject };
});
my subject always return null .
the reason that I want to use map because I've read many people say It faster than for loop
Is their anyway to use map instead of for
I'm learning about clean code can you give me an Idea how to refactor code in nested for loop
Check this up https://dev.to/jhalvorson/how-do-i-use-async-await-with-array-map-1h3f.
From this article:
You can't async/await Array.map since synchronous code won't sit and wait for your asynchronous code to resolve, instead it will the fire the function and move on. This is desired behaviour as we don't want our Array.map to block the thread otherwise nothing would run whilst Array.map goes and does its thing. Knowing that is cool, however, it doesn't really help us if we need to await our Array.map().
Thankfully there is a solution, we can achieve what we want by utilising Promise.all
//For example, the following won't work:
const postIds = ['123', 'dn28e29', 'dn22je3'];
const posts = await postIds.map(id => {
return axios
.get(`https://example.com/post/${id}`)
.then(res => res.data)
.catch(e => console.error(e));
}
console.log(posts) // [] it returns the promise, not the results 💩
//But this will:
const postIds = ['123', 'dn28e29', 'dn22je3'];
const posts = posts.map(post => {
return axios
.get(`https://example.com/post/${post.id}`)
.then(res => res.data)
.catch(e => console.error(e));
}
Promise.all(posts).then(res => console.log(`We have posts: ${res}!`));
Instead of immediately returning the Array.map we're assigning it to a variable and passing that variable to Promise.all, once that resolves we then have access to the new array returned by Array.map.
I'm trying to prefetch multiple image before navigating to another screen, but returnedStudents all undefined.
prepareStudentImages = async (students) => {
let returnedStudents = students.map(student => {
Image.prefetch(student.image)
.then((data) => {
...
})
.catch((data) => {
...
})
.finally(() => {
return student;
});
});
await console.log(returnedStudents); // ----> all items undefined
}
There are a couple of things to fix with this:
1) Your map() function does not return anything. This is why your console log is undefined.
2) Once your map functions work, you are logging an array of promises. To deal with multiple promises (an array), you can use Promise.all().
So I think to fix this, you can do:
prepareStudentImages = async (students) => {
const returnedStudents = students.map(student =>
Image.prefetch(student.image)
.then((data) => {
...
})
.catch((data) => {
...
})
.finally(() => {
return student
})
)
console.log(returnedStudents) // log the promise array
const result = await Promise.all(returnedStudents) // wait until all asyncs are complete
console.log(result) // log the results of each promise in an array
return result
}
I'm using a chain of then statements to transform data after a fetch request. The code is within a React app, but I think this is just a Javascript question.
Here's my current code:
componentDidMount(){
fetch(this.state.dataRouteDirections)
.then(data => data=data.json())
.then(data => console.log(data))
.then(data => data.unshift(this.state.emptyDirections))
.then(data => this.setState({directions:data}))
.then(()=>{
var makes = this.state.directions.map(directions=>directions.acf.make); //get all make categories
function removeDuplicates(arr){
let unique_array = arr.filter(function(elem, index, self) {
return index===self.indexOf(elem);
});
return unique_array
}
makes = removeDuplicates(makes);
this.setState({makes:makes});
})
}
The line giving me trouble is ".then(data => data.unshift(this.state.emptyDirections))". It is telling me that the variable data is undefined in this statement. Indeed, if I replace this line with the same line as above, console logging data twice, the first console log logs correctly and the second is undefined.
What am I not understanding about fetch/then/JS? I'm coming from a PHP background and async stuff is still a challenge.
Question is answered below, here's my updated code:
componentDidMount(){
fetch(this.state.dataRouteDirections)
.then(data => data=data.json())
.then(data => {data.unshift(this.state.emptyDirections); return data;})
.then(data => this.setState({directions:data}))
.then(()=>{
var makes = this.state.directions.map(directions=>directions.acf.make); //get all make categories
function removeDuplicates(arr){
let unique_array = arr.filter(function(elem, index, self) {
return index===self.indexOf(elem);
});
return unique_array
}
makes = removeDuplicates(makes);
this.setState({makes:makes});
})
}
The reason you are having this is because console.log(data) returns undefined. You should write smth like:
.then(data => {console.log(data); return data})