How fetch multiple nested relations in a Smart view

I have a rather complicated Smart view where I’m listing out the primary records for the model and I need to fetch multiple related models 3-4 levels. I followed the fetchData and Shipping status examples here and was able to get the relationships all the way down using await this.store.query(), but the entire promise doesn’t resolve properly. I’ve even tried using

`DS.PromiseArray.create({
        promise: Promise.all([`

to resolve the promises, but I’m still having promises resolve after this. Is there a better workaround for this? In our case, we have a list of TalentQueues. For each one, which will be selected one at a time (eg. current selected record), I need to get 2 different sets of Users (eg. 2 differing Promises). Both of those will have 3 different models called to fetch out notes, experiences, and other sorts of relationships (eg. more promises).

Any help would be greatly appreciated, and I’m happy to share out our code if someone from Forest Admin wants to take a peek at our production TalentQueues table.

Hello @David_Panzarella,

Could you please share some code with us? It’s difficult to identify your problem with promises with only the description you can make of the code.

Sure thing.

We need a final result from 2 primary queries (stubs & users)


// Set up a loading state
set(this, "currentRecordIsLoading", true);

// fetch the stubs
const stubsPromise = await this.store.query("forest-SuggestedTalentStub", stubsParams)
  .then(async (stubs) => {
    stubs.map(async stub => {
      const userStub = stub["forest-UserStub"];

      const stubInternalRecordsParams = {
        filters: JSON.stringify({
          aggregator: "and",
          conditions: [
            {
              field: "UserStubID",
              operator: "equal",
              value: userStub.id,
            },
          ],
        }),
        "page[number]": 1,
        "page[size]": 50,
      };

      const stubInternalRecordsPromise = await this.store.query("forest-InternalRecord", stubInternalRecordsParams);
      const stubWorkExperiencesPromise = await this.store.query("forest-WorkExperience", stubInternalRecordsParams);

      return DS.PromiseArray.create({
        promise: Promise.all([
          stubInternalRecordsPromise,
          stubWorkExperiencesPromise
        ]).then(([internalRecords, qualification, workExperiences]) => {
          stub.internalRecords = internalRecords;
          stub.workExperiences = workExperiences;
        })
      });
    });
  });

// fetch talent queue users with a similar set of nested promises above
const usersPromise = await this.store.query("forest-TalentQueuexUser", userParams);

// finally resolve all promises and merge the 2 promises above
return DS.PromiseArray.create({
  promise: Promise.all([
    stubsPromise,
    usersPromise
  ]).then(([stubs, users]) => {

    // merge both stubs and users into one array
    stubs.map(stub => {
      mergedArray.push({});
    });

    users.map(stub => {
      mergedArray.push({});
    });

    set(this, "currentRecordIsLoading", false);
    return mergedArray;
  })
});

I’ve also attempted moving stubsPromise promises into the final DS.PromiseArray, but the result is the same. currentRecordIsLoading will always get set to false before any nested promises resolve (eg. stubInternalRecordsPromise & stubWorkExperiencesPromise).

Hope this helps clarify, and thanks for the quick reply! :slight_smile:

Ok, it’s more clear here.

I can see multiple things you can improve:

  1. You are mixing calls to then and await, you could simplify everything with only await
  2. Sometimes you are calling await and also Promise.all on a value that is no more a promise
  3. Why do you need to use DS.PromiseArray? In general this utility is only necessary just before passing a value to a template. Here you are using it internally in your promise handling. You don’t need this. It’s only necessary when passing the result of a promise to the component for displaying it.

Also, I don’t understand how this part is supposed to work:

 Promise.all([
      stubInternalRecordsPromise,
      stubWorkExperiencesPromise
    ]).then(([internalRecords, qualification, workExperiences]) => {
      stub.internalRecords = internalRecords;
      stub.workExperiences = workExperiences;
    })

As you pass an array of 2 elements and expect an array of 3 in the result.

I imagined something like that, I created functions to make it more decoupled, but could not test if I did not made any typo or naming errors, just to give you the idea

async function loadStub(stub){
  const userStub = stub["forest-UserStub"];

  const stubInternalRecordsParams = {
    filters: JSON.stringify({
      aggregator: "and",
      conditions: [
        {
          field: "UserStubID",
          operator: "equal",
          value: userStub.id,
        },
      ],
    }),
    "page[number]": 1,
    "page[size]": 50,
  };

  const [stubInternalRecords, stubWorkExperiences] = await Promise.all([
    this.store.query("forest-InternalRecord", stubInternalRecordsParams),
    this.store.query("forest-WorkExperience", stubInternalRecordsParams),
  ])

  stub.internalRecords = internalRecords;
  stub.workExperiences = workExperiences;

  return stub;
}

async function loadStubs(stubsParams){
  const stubs = await this.store.query("forest-SuggestedTalentStub", stubsParams);

  return Promise.all(stubs.map(loadStub));
}

async function loadUsersAndStubs(usersParams, stubsParams){
  const [stubs, users] = await Promise.all([
    loadStubs(stubsParams),
    this.store.query("forest-TalentQueuexUser", userParams),
  ]);

  return [...stubs,... users];
}

// Set up a loading state
set(this, "currentRecordIsLoading", true);

return DS.PromiseArray.create({
  promise: loadUsersAndStubs(usersParams, stubsParams).then(
    (usersAndStubs) => {
      set(this, "currentRecordIsLoading", false);
      return usersAndStubs;
    }
  ),
});
1 Like

Ah, yes that makes much more sense to me now as well. I’m completely new to Ember world, and yes, was def mixing await with DS.Promise, which was based on some other examples I had found. I had tried with and without await as well. As you pointed out, I basically need DS.PromiseArray to pull off a loading state in the Template, correct?

For this block:

async function loadUsersAndStubs(usersParams, stubsParams){
  const [stubs, users] = await Promise.all([
    loadStubs(stubsParams),
    this.store.query("forest-TalentQueuexUser", userParams),
  ]);

  return [...stubs,... users];
}

I’ll need to also create an add’l promise for loadUserss(userParams), so that should work fine as well.

The result i’d like is:

but if all I can get is a loading state for all until it’s loaded, that’s totally fine too. The final loadUsersAndStubs will reduce each stub and user and put them into a normalized array, sorting them into differing lanes, so I think I should be able to set a loading state for each lane, but again, that is less of a concern if I can get a single loading state back to the template after all the promises resolve.

Thanks for the help! It’s been super helpful!

Got it to work like a charm!! Thanks so much!

Now I just need to get the initial loading state of the record collection to work: Smart View Initial Recordset