-1

I'm stuck in async hell:

function convertToDomainUsers(dbUsers: Array<UserDB>): Array<UserDomain> {

  // iterate each DB user and convert them to domain user types
  const domainUsers: Array<UserDomain> = dbUsers.map( async (dbUser: UserDB) => {

    // first convert the DB user to a Domain User
    const domainUser: UserDomain = newUserDomainModel(dbUser);

    // Now we need to get their user links from the DB
    const dbUserLinks: Array<UserLinkDB> = await findDBUserLinks(dbUser.user_id);

    // convert their user links to Domain user links
    const domainUserLinks: Array<UserLinkDomain> = convertToUserLinks(dbUserLinks);

    // now merry up the domain user links to the domain user  
    domainUser.links = domainUserLinks;

    return domainUser;
  });

  return domainUsers;
}

function newUserDomainModel(user: UserDB): UserDomain {
  const domainUser: UserDomain = {
    username: user.user_username,
    firstName: user.user_name_first,
    lastName: user.user_name_last
  };
    
  return domainUser;
}

async function findDBUserLinks(userId: bigint): Promise<Array<UserLinkDB>> {
  const dbUserLinks: Array<UserLinkDB> = await getUserLinks(userId);
  return dbUserLinks;
}

async function getUserLinks(id: bigint): Promise<Array<UserLinkDB>> {
  setDB();
  await client.connect();

  const query = `
    select 
      link_url,
      social_type_id
    from user_links
    WHERE user_id = ${id}`;

  const res = await client.query(query);
  const links: Array<UserLinkDB> = res.rows;

  return Promise.resolve(links);
}

Error (happening on const domainUsers: in the convertToDomainUsers function):

TS2322: Type 'Promise<UserDomain>[]' is not assignable to type 'UserDomain[]'.   Type 'Promise<UserDomain>' is missing the following properties from type 'UserDomain': username, firstName, lastName, fullName, and 6 more

comments were added for the sake of making this stack post easier to follow. I don't normally write comments, they're cruft.

PositiveGuy
  • 17,621
  • 26
  • 79
  • 138
  • Is `UserrDomain` with 2 "r" at the end of "User" a mistake? I don't know if that's the actual issue, but it seems wrong. – zero298 Oct 13 '20 at 21:10
  • sorry let me fix that, a paste error – PositiveGuy Oct 13 '20 at 21:10
  • Also, `dbUsers.map(async () => {})` is going to return a `Promise>` since anything returned by an `async` function will be wrapped in a `Promise`. So `convertToDomainUsers`'s return type is incorrect. – zero298 Oct 13 '20 at 21:13
  • 2
    @zero298 actually `Array>`, it's an array of promises not a promise of an array. – jonrsharpe Oct 13 '20 at 21:15
  • And if you want to resolve an array of promises: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all. `convertToDomainUsers` will also have to return a promise, you can't magically make it synchronous at that point. – jonrsharpe Oct 13 '20 at 21:16
  • @jonrsharpe Oops, yes got that inverted. – zero298 Oct 13 '20 at 21:20
  • errr, ok I think I see now, what a pain. I don't know if there's an easier way to go about this, but don't think so when dealing with making calls within a map like this. I thought I could somehow get out of async and yield better and make it cleaner but you really can't here – PositiveGuy Oct 13 '20 at 21:20
  • 1
    It's still much less of a pain than `.then()` callbacks nested 6 deep. – zero298 Oct 13 '20 at 21:21
  • agree, definitely less pain. I guess mapping DB entities to Domain entities is just not a lot of fun :). I could have also doin joins in my query but I hate that. Would rather just get the child data via separate calls and merry up at the end for a complex type like this – PositiveGuy Oct 13 '20 at 21:22
  • Are you just missing an await - the map function is async. So should it be const domainUsers: Array = await dbUsers.map ... – Woody Oct 13 '20 at 21:23
  • @Woody no, you can't just await an Array.map – jonrsharpe Oct 13 '20 at 21:23
  • Woody: I do have an async inside the map `await findDBUserLinks` – PositiveGuy Oct 13 '20 at 21:25
  • Right - so the problem is that an async function automatically wraps the return value in a Promise. So you are mapping into an Array of promises just like the compiler is telling you. So you will have to then await all those promises. – Woody Oct 13 '20 at 21:27
  • 1
    @PositiveGuy yes, the callbacks are async, that's what's making it an array of promises. See e.g. https://stackoverflow.com/q/40140149/3001761, you can't just await the array .map returns. – jonrsharpe Oct 13 '20 at 21:28
  • just trying to figure outnow how to promise.all on it. So the caller `const domainUsers = convertToDomainUsers(dbUsers);` – PositiveGuy Oct 13 '20 at 21:29
  • thanks, much @jonrsharpe my head hurts. `await Promise.all(...)` – PositiveGuy Oct 13 '20 at 21:30
  • 1
    await Promise.all(dbUsers.map(...) should do it I think – Woody Oct 13 '20 at 21:31

1 Answers1

0

Calling:

const domainUsers = await Promise.all(convertToDomainUsers(dbUsers));

Working implementation:

function convertToDomainUsers(dbUsers: Array<UserDB>): Array<Promise<UserDomain>> {

  const domainUsers: Array<Promise<UserDomain>> = dbUsers.map( async (dbUser: UserDB) => {

    const domainUser: UserDomain = newUserDomainModel(dbUser);

    const dbUserLinks: Array<UserLinkDB> = await findDBUserLinks(dbUser.user_id);

    const domainUserLinks: Array<UserLinkDomain> = convertToUserLinks(dbUserLinks);

    domainUser.links = domainUserLinks;

    return domainUser;
  });

  return domainUsers;
}
PositiveGuy
  • 17,621
  • 26
  • 79
  • 138
  • Again, that returns an array of promises *not* a promise of an array. If it returned a promise `Promise.all` wouldn't work on it. That's even what you've declared `domainUsers` as, but isn't the return type; this wouldn't pass type checking. – jonrsharpe Oct 13 '20 at 22:42