Reputation: 5971
I want to do the following:
const contextSeeder = new ContextSeeder(context);
let props = (await (await contextSeeder.withList()).withUser()).get();
But ideally this would look like:
const contextSeeder = new ContextSeeder(context);
let props = await contextSeeder.withList().withUser().get();
withList()
and withUser()
will both make an API call. So ideally they should run in parallel. They both modify an object in the class which is the return value (could there be a race condition?)
So given that I call the two "loaders", I'd want the .get()
to be the one that must actually be waited for.
Is it possible? Because in the first way it looks like it will handle one by one (because of the await in both calls)
I run this inside an async function
, and I need this data to be present by the time the function returns. I can't do a .then()
Thanks
export default class ContextSeeder {
context = {};
serverContext;
constructor(serverContext = undefined) {
this.serverContext = serverContext;
}
withUser = async (listId = undefined) => {
let userContext = {};
await UserContextSeeder(this.serverContext).then(context => {userContext = context});
this.context = {...this.context, user: userContext}
return this;
}
withList = async (userId = undefined) => {
let listContext = {};
await ListContextSeeder(context).then(context => {listContext = context});
this.context = {...this.context, list: listContext}
return this;
}
get = () => {
return this.context;
}
}
Upvotes: 0
Views: 73
Reputation: 5971
I made a different approach, based on the answers I got here and Any difference between await Promise.all() and multiple await?
I did not want to await for every call independently, because that would make them to be serialized instead of parallel.
So I made sure to remove all the await
and leave only one:
export async function getServerSideProps(context) {
const contextSeeder = new ContextSeeder(context);
let props = await contextSeeder.withList(335030).withUser().get();
return {
props: props,
}
}
This way I got what I wanted, and performance wise it is better.
ContextSeeder.js
import UserContextSeeder from "./UserContextSeeder";
import ListContextSeeder from "./ListContextSeeder";
/**
*
*/
export default class ContextSeeder {
returnContext = {};
serverContext;
withListEnabled = false;
listId;
withUserEnabled = false;
userId;
constructor(serverContext = undefined) {
this.serverContext = serverContext;
}
withList = (listId = undefined) => {
this.withListEnabled = true;
this.listId = listId;
return this;
}
withUser = (userId = undefined) => {
this.withUserEnabled = true;
this.userId = userId;
return this;
}
_withList = async () => {
return ListContextSeeder(this.listId)
.then(response => response.json())
.then(listData => {this.returnContext.list = listData});
}
_withUser = async (userId) => {
return UserContextSeeder(this.serverContext, userId)
.then(response => response.json())
.then(userData => {this.returnContext.user = userData});
}
get = async () => {
let promises = [];
if (this.withListEnabled) {
promises.push(this._withList(this.listId));
}
if (this.withUserEnabled) {
promises.push(this._withUser(this.userId));
}
await Promise.all(promises.map(p => p.catch(e => e)))
.then(results => console.log(results))
.catch(e => console.log(e));
return this.returnContext;
}
}
ListContentSeeder.js
export default async function ListContextSeeder(listId) {
const listManager = new ListManager();
return listManager.getList(listId);
}
ListManager.getList(listId)
simply returns an isomorphic-unfetch
promise.
Upvotes: 0
Reputation: 55749
The following kicks-off the async retrievals one by one, and then waits for them to complete in parallel using Promise.all
. Finally, the results are destructured and then added to an object to be returned.
I think this is a simplification of your code, because intermediate state is not shared publicly.
I would rename ListContextSeeder
and UserContextSeeder
to be something more idiomatic. Capitalized names are usually reserved for constructor functions.
export default async function seedContext({ serverContext, listId, userId }) {
const p1 = ListContextSeeder(serverContext, listId)
const p2 = UserContextSeeder(serverContext, userId)
const [list, user] = await Promise.all([p1, p2])
return { list, user }
}
Upvotes: 1
Reputation: 286
For the purpose of this answer, I'll ignore the fact that you're not using userId nor listId.
You could simplify your code with the following:
export default class ContextSeeder {
context = {};
serverContext;
constructor(serverContext = undefined) {
this.serverContext = serverContext;
}
withList = async (listId = undefined) => {
let seededUserContext = await UserContextSeeder(this.serverContext);
this.context = {...this.context, user: seededUserContext}
// Not needed unless you still want to chain them
// return this;
}
withUser = async (userId = undefined) => {
let seededListContext = await ListContextSeeder(context);
this.context = {...this.context, list: seededListContext}
// Not needed unless you still want to chain them
// return this;
}
get = () => {
return this.context;
}
}
Then, you can await the resolution of both by using Promise.all():
// They now run in "parallel"
await Promise.all([contextSeeder.withList(), contextSeeder.withUser());
let props = contexSeeder.get();
Based on Ben Aston's answer below, another improvement would be to encapsulate this behaviour in another internal method, pass an intermediate context and only set the actual context once both the calls are done.
export default class ContextSeeder {
context = {};
serverContext;
constructor(serverContext = undefined) {
this.serverContext = serverContext;
}
withList = async (listId = undefined, intermediateContext) => {
let seededUserContext = await UserContextSeeder(this.serverContext);
// No spread operator needed, just set the property
intermediateContext.user = seededUserContext;
// Not needed unless you still want to chain them
// return this;
};
withUser = async (userId = undefined, intermediateContext) => {
let seededListContext = await ListContextSeeder(context);
// No spread operator needed, just set the property
intermediateContext.list = seededListContext;
// Not needed unless you still want to chain them
// return this;
};
seedContext = async (userId, listId) => {
let intermediateContext = {};
await Promise.all([
withUser(userId, intermediateContext),
withList(listId, intermediateContext)
]);
this.context = intermediateContext;
}
get = () => {
return this.context;
};
}
Then, simply await seedContext:
await contextSeeder.seedContext(userId, listId);
let props = contextSeeder.get();
Upvotes: 1
Reputation: 35522
If you want to run the requests in parallel, then wait for them both to finish before calling .get(), you can try the following code using Promise.all
:
// we wait for both promises to finish
await Promise.all([contextSeeder.withList(), contextSeeder.withUser()]);
// they're both finished so we can get it now
let props = contextSeeder.get();
Upvotes: 1