Reputation: 13
I have been tormented with this problem for 2 days, but did not dare to ask this question. I read a lot of documentation about promises and asynchronous functions. Perhaps the answer to this question will be useful to other users.
I want to call the details of a detail item in a list of items.
The list
array looks like this:
[
{
"id": 1,
"name: "A",
},
{
"id": 2,
"name: "B",
},
...
]
The one
data I want to call inside list
array:
{
"id": 1,
"url: "aaa",
"props": ["a", "a", "a"]
}
react-admin Promises:
const list = dataProvider.getList(resource, params);
const one = (id: number) => dataProvider.getOne(resource, { id })
dataProvider
return a Promise for an object with a data
property.
I wrote a function that returns data in parallel:
const data = async () => {
const list = await dataProvider.getList(resource, params);
const one = (id: number) => dataProvider.getOne(resource, { id });
list.data = await Promise.all(
list.data.map(async (item) => {
const itemOne = await one(item.id);
return {
...item,
custom: {
test: itemOne.data.url,
},
};
})
);
return list;
};
console.log(data())
Now I'm trying to use a reduce
instead of a map
.
Please help me find the right solution or direction.
Upvotes: 1
Views: 95
Reputation: 1073978
But in this case, the promises are processed all at once, and not sequentially, so they make incredibly long page load time.
Now I'm trying to use a reduce instead of a map.
It's unlikely that doing them in series (one after another) instead of in parallel (as you are now) would make anything faster; it's much more likely that it would make the page load take longer. But if you want to do those operations in series (one after another) rather than in parallel (all happening concurrently), reduce
is over-complicated for that (and nearly everything else). Just use a for-of
loop. I've included an example at the end of the answer.
I haven't used react-admin, but whenever getting a bunch of things from an API one-by-one is slow, I look to see if I can ask for them all at once instead. Looking at the documentation, you can use getMany
instead of getOne
. Something like this:
const data = async () => {
// Get the list
const list = await dataProvider.getList(resource, params);
// Get the items
const items = await dataProvider.getMany(resource, { ids: list.data.map(({id}) => id) });
// Extracting each `id` from the item to create the array −−−−−−−−−^^^^^^^^^^^^^^^^^
// Add those to the list items
list.data = list.data.map((item, index) => {
return {
...item,
custom: {
test: items.data[index].url,
},
};
});
return list;
};
Note that that assumes that the data that comes back from getMany
is in the same order as the ids
array. The documentation seems to suggest that in the examples, but sadly doesn't actually say it.
If it turns out that it doesn't give them to you in the order you asked for them, you'll probably want to make a map keyed by id
:
const data = async () => {
// Get the list
const list = await dataProvider.getList(resource, params);
// Get the items
const items = await dataProvider.getMany(resource, { ids: list.data.map(({id}) => id) });
// Extracting each `id` from the item to create the array −−−−−−−−−^^^^^^^^^^^^^^^^^
// Build the map of those items keyed by `id`
const map = new Map(items.data.map(item => [item.id, item]));
// Add those to the list items
list.data = list.data.map((item, index) => {
return {
...item,
custom: {
test: map.get(item.id).url,
},
};
});
return list;
};
Here's that for-of
loop to do the work in series, but again, I think it's likely to make the problem worse, not better.
const data = async () => {
const list = await dataProvider.getList(resource, params);
const data = [];
for (const item of list.data) {
const itemOne = await dataProvider.getOne(resource, { id: item.id })
data.push({
...item,
custom: {
test: itemOne.data.url,
},
});
}
list.data = data;
return list;
};
Upvotes: 1