So I'm pretty sure this have something to do with Promise.all or something like that but I'm not sure how to do it. will appreciate if someone can help me out with this code.
Note: the array I'm trying to map is an array of objects if that matters
const productsArray = this.cartProducts.map(product =>
fetch(`/api/products/getDetails/${product.clientId}`))
.then(res => res.json())
.then(data => {
console.log(data)
})
I'm not able to even launch this code but I'm not sure what I should do exactly...
This is my error from visual studio code.
property 'then' does not exist on type 'promise<response>[]'
fetch() result is promise so you need to use Promise.all()
const promises = await Promise.all(this.cartProducts.map(product => fetch(`/api/products/getDetails/${product.clientId}`))
const productsArray = await Promise.all(promises.map(p => p.json()))
William Wang's answer is perfectly good but you may want to use async/await to like this:
const productsArray = this.cartProducts.map(async product => {
const res = await fetch(`/api/products/getDetails/${product.clientId}`);
const data = res.json();
return data;
});
It may be simplified with:
const productsArray = this.cartProducts.map(async product => {
return await fetch(`/api/products/getDetails/${product.clientId}`).json();
});
However be aware that this kind of pattern will be more "synchronous" than using a Promise.all pattern, as every product will be fetch only after the previous one is fetched. Promise.all will fetch all the product on parallel.
TL;DR: Map each item in the array to a fetch call and wrap them around Promise.all().
Promise.all(
this.cartProducts.map(p =>
fetch(`/api/products/getDetails/${p.clientId}`).then(res => res.json())
)
).then(products => console.log(products));
Explanation
fetch() returns a Promise a.k.a "I promise I'll give you a value in the future". When the time comes, that future value is resolved and passed to the callback in .then(callback) for further processing. The result of .then() is also a Promise which means they're chainable.
// returns Promise
fetch('...')
// also returns Promise
fetch('...').then(res => res.json())
// also returns Promise where the next resolved value is undefined
fetch('...').then(res => res.json()).then(() => undefined)
So the code below will return another Promise where the resolved value is a parsed Javascript object.
fetch('...').then(res => res.json())
Array.map() maps each item to the result of the callback and return a new array after all callbacks are executed, in this case an array of Promise.
const promises = this.cartProducts.map(p =>
fetch("...").then(res => res.json())
);
After calling map, you will have a list of Promise waiting to be resolved. They do not contains the actual product value fetched from the server as explained above. But you don't want promises, you want to get an array of the final resolved values.
That's where Promise.all() comes into play. They are the promise version of the Array.map() where they 'map' each Promise to the resolved value using the Promise API.
Because of that Promise.all() will resolve yet another new Promise after all of the individual promises in the promises array have been resolved.
// assuming the resolved value is Product
// promises is Promise[]
const promises = this.cartProducts.map(p =>
fetch("...").then(res => res.json())
);
Promise.all(promises).then(products => {
// products is Product[]. The actual value we need
console.log(products)
})
Related
I'm trying to work with data sent from postman, array of objects.
I need to write all data into database, so I use mapping of array, and get undefined
const providers = await req.body.providers.map((provider) => {
ProviderService.createProvider(provider.name, container._id);
});
Promise.all(providers).then((value) => {
console.log(value);
});
I get from console log array of undefined, but items are creating in Data Base, I know I have mistake in asynchronous functions, but I don't really understand - where
Thank you for answer
if I got this right, when you use the await keyword inside async function you actually wait for the promise to be resolved then you assign its extracted value to the providers variable.
Promise.all takes as parameter an array of promises, so I guess this is where things go wrong when you send it an array with values.
Your first statement is returning an Array. You can only use await on promises.
Promise.all() does return a promise though.
So you just need to move the await to the correct place.
const providers = req.body.providers.map((provider) => {
ProviderService.createProvider(provider.name, container._id);
});
await Promise.all(providers).then((value) => {
console.log(value);
});
I've been messing around with the fetch() api recently, and noticed something which was a bit quirky.
let url = "http://jsonplaceholder.typicode.com/posts/6";
let iterator = fetch(url);
iterator
.then(response => {
return {
data: response.json(),
status: response.status
}
})
.then(post => document.write(post.data));
;
post.data returns a Promise object.
http://jsbin.com/wofulo/2/edit?js,output
However if it is written as:
let url = "http://jsonplaceholder.typicode.com/posts/6";
let iterator = fetch(url);
iterator
.then(response => response.json())
.then(post => document.write(post.title));
;
post here is a standard Object which you can access the title attribute.
http://jsbin.com/wofulo/edit?js,output
So my question is: why does response.json return a promise in an object literal, but return the value if just returned?
Why does response.json return a promise?
Because you receive the response as soon as all headers have arrived. Calling .json() gets you another promise for the body of the http response that is yet to be loaded. See also Why is the response object from JavaScript fetch API a promise?.
Why do I get the value if I return the promise from the then handler?
Because that's how promises work. The ability to return promises from the callback and get them adopted is their most relevant feature, it makes them chainable without nesting.
You can use
fetch(url).then(response =>
response.json().then(data => ({
data: data,
status: response.status
})
).then(res => {
console.log(res.status, res.data.title)
}));
or any other of the approaches to access previous promise results in a .then() chain to get the response status after having awaited the json body.
This difference is due to the behavior of Promises more than fetch() specifically.
When a .then() callback returns an additional Promise, the next .then() callback in the chain is essentially bound to that Promise, receiving its resolve or reject fulfillment and value.
The 2nd snippet could also have been written as:
iterator.then(response =>
response.json().then(post => document.write(post.title))
);
In both this form and yours, the value of post is provided by the Promise returned from response.json().
When you return a plain Object, though, .then() considers that a successful result and resolves itself immediately, similar to:
iterator.then(response =>
Promise.resolve({
data: response.json(),
status: response.status
})
.then(post => document.write(post.data))
);
post in this case is simply the Object you created, which holds a Promise in its data property. The wait for that promise to be fulfilled is still incomplete.
In addition to the above answers here is how you might handle a 500 series response from your api where you receive an error message encoded in json:
function callApi(url) {
return fetch(url)
.then(response => {
if (response.ok) {
return response.json().then(response => ({ response }));
}
return response.json().then(error => ({ error }));
})
;
}
let url = 'http://jsonplaceholder.typicode.com/posts/6';
const { response, error } = callApi(url);
if (response) {
// handle json decoded response
} else {
// handle json decoded 500 series response
}
Also, what helped me understand this particular scenario that you described is the Promise API documentation, specifically where it explains how the promised returned by the then method will be resolved differently depending on what the handler fn returns:
if the handler function:
returns a value, the promise returned by then gets resolved with the returned value as its value;
throws an error, the promise returned by then gets rejected with the thrown error as its value;
returns an already resolved promise, the promise returned by then gets resolved with that promise's value as its value;
returns an already rejected promise, the promise returned by then gets rejected with that promise's value as its value.
returns another pending promise object, the resolution/rejection of the promise returned by then will be subsequent to the
resolution/rejection of the promise returned by the handler. Also, the
value of the promise returned by then will be the same as the value of
the promise returned by the handler.
The json() method is available on all fetch() function. The json() method returns a Promise. Remember, when returning a Promise, it is still pending because it is asynchronous (assuming the data is not there yet). So to get the data AFTER using the json() method, you need to use another then() method so it will just return the data after it arrives.
To answer your question, It is what it is, its just the way of doing that.
Its like Promise ---> Another Promise ----> data
Use await with responce.json() also
const responce = await fetch(url);
const result = await responce.json();
Use await with responce.json() also
So I am looking at this code from https://javascript.info/promise-api.
let names = ['iliakan', 'remy', 'jeresig'];
let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));
Promise.all(requests)
.then(responses => {
// all responses are resolved successfully
for(let response of responses) {
alert(`${response.url}: ${response.status}`); // shows 200 for every url
}
return responses;
})
// map array of responses into an array of response.json() to read their content
.then(responses => Promise.all(responses.map(r => r.json())))
Why does the .then() pass the response to yet another Promise.all?
.then(responses => Promise.all(responses.map(r => r.json())))
I tried to simplify it to the code below but it just throws undefined error:
.then(responses => responses.map(r => r.json()))
Why is that?
requests is an array of promises and you want to iterate over them once they are all resolved.
The Promise.all() method takes an iterable of promises as an input,
and returns a single Promise that resolves to an array of the results
of the input promises. This returned promise will resolve when all of
the input's promises have resolved, or if the input iterable contains
no promises. It rejects immediately upon any of the input promises
rejecting or non-promises throwing an error, and will reject with this
first rejection message / error.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
The second Promise.all is acting on this array of promises:
Promise.all(responses.map(r => r.json())))
If they didn't use Promise.all here, then the promises would not be resolved in the next then call.
You could refactor the function so they are json'd from the start like so:
let names = ['iliakan', 'remy', 'jeresig'];
let requests = names.map(
name => fetch(`https://api.github.com/users/${name}`)
.then(r=>r.json())
);
Promise.all(requests)
.then(content => {
// do something with content directly
});
But then you wouldn't be able to log the url and status like they are.
Perhaps this is a limitation of the language, but I am trying to figure out how I could get away with using a single await keyword for a set of sequential promise resolutions. I am trying to achieve something readable, like the following:
const workoutExercises = await Workout.get(1).workoutExercises;
Workout.get accesses a database, and so returns a Promise. The promise resolves to an instance of Workout, which has a getter method on the instance called workoutExercises, which is also a Promise that resolves to an array of WorkoutExercises.
The above code does not work, and only waits for Workout.get to resolve; doesn't also wait for .workoutExercises to resolve. The following below code examples DO work, but I am trying to achieve a one-liner / more readability:
1:
const workoutExercises = await (await Workout.get(1)).workoutExercises;
2:
const workout = await Workout.get(1);
const workoutExercises = await workout.workoutExercises;
Edit #1
Updated the title and description to clarify that the problem doesn't revolve around the resolution of a Promise chain, but the resolution of a Promise based on the resolution of a preceding Promise.
Workout.get --> <Promise> --> workout --> .workoutExercises --> <Promise> -> desired result
Use .then() on the results of .get(1), and extract workoutExercises. The one liner is very readable.
Example:
(async() => {
const Workout = {
get: () => Promise.resolve({
workoutExercises: Promise.resolve(3)
})
};
const workoutExercises = await Workout.get(1).then(w => w.workoutExercises);
console.log(workoutExercises)
})()
Is it possible to use one await keyword for resolving a set of sequential promises
Because these promises are not actually chained, it is not. You have a promise that resolves to an object that has a property that when you access it has a getter that creates a new promise that resolves to the value you finally want. That's two completely separate promises and you'll have to use await or .then() on each of the two so you can't do this with one single await.
You can use a little intervening code to chain them together and even put them into a helper function:
function getWorkoutExercises(n) {
return Workout.get(n).then(workout => {
// chain this promise to the previous one
return workout.workoutExercises;
});
}
// usage
getWorkoutExercises(1).then(exercises => {
console.log(exercises);
}).catch(err => {
console.log(err);
});
or
// usage
try {
let exercises = await getWorkoutExercises(1);
console.log(exercises);
} catch(e) {
console.log(e);
}
I have the following
const ingredientsPromises = ingredients.map(ingredient =>
// Is this .map calling the api?
axios.post('/api/ingredient', ingredient)
)
await Promise.all(ingredientsPromises)
.then(consoleThen)
.catch(consoleCatch)
I'm making multiple calls to an api, but what I'm not sure is that when the promise will run, will it run inside .map or only in Promise.all?
Promise will run individually. It is up to you when do you want to do something with it.
If you want to do something after each individual request is completed then you can just use
const ingredientsPromises = ingredients.map(ingredient =>
// Is this .map calling the api?
axios.post('/api/ingredient', ingredient)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
)
Otherwise you can use the promise returned by 'all' function like this. If you want to do something after all promises are resolved.
const ingredientsPromises = ingredients.map(ingredient =>
// Is this .map calling the api?
axios.post('/api/ingredient', ingredient)
)
await Promise.all(ingredientsPromises)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
Also await won't do anything here so you can remove it.
const ingredientsPromises = ingredients.map(ingredient =>
// Is this .map calling the api?
axios.post('/api/ingredient', ingredient)
)
Yes, this is initiating your API request(s).
A promise will always return a promise.
Lets see what's happening here,
First of all, ingredientsPromises is initialized with array of promises (each promise representing your API request in pending state).
At this point of time, all of your requests had been initiated.
from the docs,
The Promise.all() method returns a single Promise that resolves when all of the promises passed as an iterable have resolved or when the iterable contains no promises. It rejects with the reason of the first promise that rejects.
So, with Promise.all(ingredientsPromises) you are resolving all these pending promises, which returns another promise (which means you have received the API response) either in fulfilled state (in case no error occurs), or in rejected state (in case one or more requests fail)
I use bluebird map function for this
await bluebird.map(ingredients, (i) => axios.post('/api/ingredient', i))