How can I fetch an array of URLs with Promise.all?
Yes, Promise.all
is the right approach, but you actually need it twice if you want to first fetch
all urls and then get all text
s from them (which again are promises for the body of the response). So you'd need to do
Promise.all(urls.map(u=>fetch(u))).then(responses =>
Promise.all(responses.map(res => res.text()))
).then(texts => {
…
})
Your current code is not working because forEach
returns nothing (neither an array nor a promise).
Of course you can simplify that and start with getting the body from each response right after the respective fetch promise fulfilled:
Promise.all(urls.map(url =>
fetch(url).then(resp => resp.text())
)).then(texts => {
…
})
or the same thing with await
:
const texts = await Promise.all(urls.map(async url => {
const resp = await fetch(url);
return resp.text();
}));
How can I fetch an array of URLs without Promise.all?
The following solution relies on the second Stack Snippet of
this helpful answer.
1
const namesonly = ['two', 'three'];
const urls = ['https://jsonplaceholder.typicode.com/todos/2',
'https://jsonplaceholder.typicode.com/todos/3'];
const titles = [];
let countDown = urls.length;
urls.forEach((url, index) => {
asynchronousCall(url, title => {
titles[index] = title;
if (--countDown === 0) { // Callback for ALL starts on next line.
const names = namesonly.map(value => ({ name: value }));
const fakeLatins = titles.map(value => ({ loremipsum: value }));
const result =
names.map((item, i) => Object.assign({}, item, fakeLatins[i]));
console.log('result:\n' + JSON.stringify(result));
}
});
});
function asynchronousCall (url, callback) {
console.log('Starting fetch for "' + url + '".');
fetch(url).then(response => response.json()).then(responseBody => {
console.log(url.slice(-1) + ': ' + responseBody.title + ' ...');
console.log('... fetch for ' + url + ' completed!');
callback(responseBody.title); // Individual callback.
});
}
.as-console-wrapper { max-height: 100% !important; top: 0; }
Using Promise.all() to fetch a list of urls with await statements
You have several problems with your code on a fundamental basis. We should address those in order and the first is that you're not passing in any URLS!
async function fetchAll(urls) {
let data
let ext
try {
data = await Promise.all(urls.map(url=>fetch(url)))
} catch (err) {
console.log(err)
}
try {
ext = await Promise.all(data.map(res => {
if (res.json()==! 'undefined') { return res.json()}
}))
} catch (err) {
console.log(err)
}
for (let item of ext) {
console.log(ext)
}
}
First you have several try catch blocks on DEPENDANT DATA. They should all be in a single try catch block:
async function fetchAll(urls) {
try {
let data = await Promise.all(urls.map(url=>fetch(url)))
let ext = await Promise.all(data.map(res => {
// also fixed the ==! 'undefined'
if (res.json() !== undefined) { return res.json()}
}))
for (let item of ext) {
console.log(ext)
}
} catch (err) {
console.log(err)
}
}
Next is the problem that res.json() returns a promise wrapped around an object if it exists
if (res.json() !== undefined) { return res.json()}
This is not how you should be using the .json() method. It will fail if there is no parsable json. You should be putting a .catch on it
async function fetchAll(urls) {
try {
let data = await Promise.all(urls.map(url => fetch(url).catch(err => err)))
let ext = await Promise.all(data.map(res => res.json ? res.json().catch(err => err) : res))
for (let item of ext) {
console.log(ext)
}
} catch (err) {
console.log(err)
}
}
Now when it cannot fetch a URL, or parse a JSON you'll get the error and it will cascade down without throwing. Now your try catch block will ONLY throw if there is a different error that happens.
Of course this means we're putting an error handler on each promise and cascading the error, but that's not exactly a bad thing as it allows ALL of the fetches to happen and for you to distinguish which fetches failed. Which is a lot better than just having a generic handler for all fetches and not knowing which one failed.
But now we have it in a form where we can see that there is some better optimizations that can be performed to the code
async function fetchAll(urls) {
try {
let ext = await Promise.all(
urls.map(url => fetch(url)
.then(r => r.json())
.catch(error => ({ error, url }))
)
)
for (let item of ext) {
console.log(ext)
}
} catch (err) {
console.log(err)
}
}
Now with a much smaller footprint, better error handling, and readable, maintainable code, we can decide what we eventually want to return. Now the function can live wherever, be reused, and all it takes is a single array of simple GET URLs.
Next step is to do something with them so we probably want to return the array, which will be wrapped in a promise, and realistically we want the error to bubble since we've handled each fetch error, so we should also remove the try catch. At that point making it async no longer helps, and actively harms. Eventually we get a small function that groups all URL resolutions, or errors with their respective URL that we can easily filter over, map over, and chain!
function fetchAll(urls) {
return Promise.all(
urls.map(url => fetch(url)
.then(r => r.json())
.then(data => ({ data, url }))
.catch(error => ({ error, url }))
)
)
}
Now we get back an array of similar objects, each with the url it fetched, and either data or an error field! This makes chaining and inspecting SUPER easy.
Using Promise.all to resolve fetch requests
I can't see any problems, for me it returns just fine: https://jsfiddle.net/np5bx03j/
However, this is a test with jsfiddles /echo/json URLs and not your original ones. I therefore would assume some error occured in your case.
I suggest adding a catch
to log errors:
Promise.all(requestsArray.map((request) => {
return fetch(request).then((response) => {
return response.json();
}).then((data) => {
return data;
});
})).then((values) => {
console.log('values', values);
}).catch(console.error.bind(console));
EDIT: Just for the sake of completeness: I can't see any problems according to the API (MDN) or anything else either.
Promise.all fetch continue executing after throwing error?
I would recommend encapsulating the success / failure logic for individual requests, then you can determine all the resolved and rejected responses based on the result of that encapsulation.
For example
const checkKeyword = async (ref, keyword) => {
const params = new URLSearchParams({ search: keyword });
const res = await fetch(
`https://developer.wordpress.org/wp-json/wp/v2/wp-parser-${encodeURIComponent(
ref
)}?${params}`
);
if (!res.ok) {
throw new Error(`${res.status}: ${await res.text()}`);
}
const data = await res.json();
if (data.length === 0) {
throw new Error(`Empty results for '${ref}'`);
}
return { ref, data };
};
Now you can use something like Promise.any() or Promise.allSettled() to find the first successful request or all successful requests, respectively
const keyword = "AtomParser";
const refs = ["function", "hook", "class", "method"];
const promises = refs.map((ref) => checkKeyword(ref, keyword));
// First success
Promise.any(promises)
.then(({ ref, data }) => {
console.log(ref, data);
})
.catch(console.error);
// All successes
Promise.allSettled(promises)
.then((responses) =>
responses.reduce(
(arr, { status, value }) =>
status === "fulfilled" ? [...arr, value] : arr,
[]
)
)
.then((results) => {
// results has all the successful responses
});
How to return the Promise.all fetch api json data?
Aparently aa.json()
and bb.json()
are returned before being resolved, adding async/await
to that will solve the problem :
.then(async([aa, bb]) => {
const a = await aa.json();
const b = await bb.json();
return [a, b]
})
Promise.all([ fetch('https://jsonplaceholder.typicode.com/todos/1'), fetch('https://jsonplaceholder.typicode.com/todos/2') ]).then(async([aa, bb]) => { const a = await aa.json(); const b = await bb.json(); return [a, b] }) .then((responseText) => { console.log(responseText);
}).catch((err) => { console.log(err); });
Related Topics
Most Elegant Way to Force a Textarea Element to Line-Wrap, *Regardless* of Whitespace
Getelementsbyclassname to Change the Style of Elements When Event Occurs
How to Add "Href" Attribute to a Link Dynamically Using JavaScript
What Is a Text Node, Its Uses? //Document.Createtextnode()
Bootstrap and Jqueryui Conflict
The Purpose of Starting an Initial Comment with /*! in JavaScript and CSS Files
Mui Createtheme Is Not Properly Passing Theme to Mui Components
How to Optimize Website for Touch Devices
How to Retain the Scroll Position of a Scrollable Area When Pressing Back Button
Making Sure at Least One Checkbox Is Checked
How to Append Timestamp to the JavaScript File in <Script> Tag Url to Avoid Caching
How to Access Styles from React
How to Zoom a Background Image on a Div with Background-Size
How to Select All Text in Contenteditable Div
Get the Dom Path of the Clicked <A>
Cannot Use Attr with an Object in D3 V4