'Apollo Server Resolver not returning all data (returned data is not complete)


My setup: Apollo server with express.js MongoDB with Mongoose

My problem: When I run a query, my resolver is not fetching all of the data, just part of it.

Here is my resolver code:

getMarsContentForScreen: async (_, { screen, token }, context) => {
  if (!context.screen) return {};
  console.log(screen, token);
  const contentOut = {};
  const screenExist = await MarsScreen.findOne({
    name: screen,
    token: token,
  });
  if (screenExist) {
    const content = await MarsContent.findOne({
      screens: { $in: screenExist.id },
    });
    if (content) {
      // ID
      contentOut.id = content.id;
      // NAME
      contentOut.name = content.name;
      // ENTRY
      contentOut.entry = [{ entryVideos: [] }];
      content.entry.map(async (val) => {
        let file = await Asset.findById(val, 'uri');
        if (file && file.uri) {
          contentOut.entry[0].entryVideos.push(
            file.uri.split('/').slice(-1)[0]
          );
        }
      });
      // EQUIPMENT
      contentOut.equipment = [];
      content.equipment.map(async (val) => {
        let equipment = await MarsEquipment.findById(
          val.id,
          'name thumbnail background'
        );
        if (equipment) {
          contentOut.equipment.push({
            id: val.id,
            name: equipment.name,
            panelImage: equipment.thumbnail.split('/').slice(-1)[0],
            productImage: equipment.background.split('/').slice(-1)[0],
          });
        }
      });

      // EXERCISES
      contentOut.exercises = [];
      content.exercises.map(async (val, index) => {
        contentOut.exercises.push({
          equipment: val.equipment,
          content: [],
        });
        val.content.map(async (valC) => {
          let exercise = await MarsExercise.findById(
            valC.id,
            'name level text thumbnail video'
          );
          if (exercise) {
            let instructions = [];
            for (const [key, value] of Object.entries(
              JSON.parse(exercise.text)
            )) {
              instructions.push(value);
            }
            contentOut.exercises[index].content.push({
              id: valC.id,
              position: valC.position,
              name: exercise.name,
              level: exercise.level,
              instructions: instructions,
              panelImage: exercise.thumbnail.split('/').slice(-1)[0],
              programVideo: exercise.video.split('/').slice(-1)[0],
            });
          }
        });
      });

      // OPTIONS
      contentOut.options = [];
      let bgImage = await Asset.findById(content.options[0].bgImage, 'uri');
      bgImage = bgImage.uri.split('/').slice(-1)[0];
      contentOut.options = [
        {
          bgImage: bgImage,
          cards: [],
        },
      ];
      content.options[0].cards.map(async (val, index) => {
        let cardImg = await Asset.findById(val.panelImage, 'uri');
        if (cardImg) {
          contentOut.options[0].cards.push({
            name: val.name,
            panelImage: cardImg.uri.split('/').slice(-1)[0],
            subheading: val.subheading,
            action: val.action,
          });
          if (val.overlay) {
            contentOut.options[0].cards[index].overlay = val.overlay;
          }
          if (
            val.externalApp &&
            val.externalApp.appName &&
            val.externalApp.playStoreId
          ) {
            contentOut.options[0].cards[index].externalApp = {
              appName: val.externalApp.appName,
              playStoreId: val.externalApp.playStoreId,
            };
          }
        }
      });

      // WORKOUTS
      contentOut.workouts = [];
      content.workouts.map(async (val) => {
        let workout = await MarsWorkout.findById(
          val.id,
          'name thumbnail video text required'
        );
        if (workout) {
          contentOut.workouts.push({
            id: val.id,
            position: val.position,
            name: workout.name,
            panelImage: workout.thumbnail.split('/').slice(-1)[0],
            programVideo: workout.video.split('/').slice(-1)[0],
            instructions: workout.text,
            required: workout.required,
          });
        }
      });

      // FILES
      contentOut.files = [];
      content.files.map(async (val) => {
        let file = await Asset.findById(val, 'uri updated_at');
        if (file) {
          contentOut.files.push({
            id: val,
            uri: file.uri,
            filename: file.uri.split('/').slice(-1)[0],
            timestamp: file.updated_at,
          });
        }
      });
      return contentOut;
    } else {
      return {};
    }
  }
}

Here is the query I'm running in the Playground:

query {
  getMarsContentForScreen(screen: "GS123123123123", token: "token-here") {
    id
    name
    entry {
      entryVideos
    }
    equipment {
      id
      name
      position
      panelImage
      productImage
    }
    exercises {
      equipment
      content {
        id
        position
        name
        level
        panelImage
        programVideo
        instructions
      }
    }
    options {
      bgImage
      cards {
        name
        panelImage
        subheading
        action
        overlay
        externalApp {
          appName
          playStoreId
        }
      }
    }
    workouts {
      id
      position
      name
      panelImage
      programVideo
      required
      instructions
    }
    files {
      id
      filename
      uri
      timestamp
    }
  }
}

And here is the output of what I'm getting:

{
  "data": {
    "getMarsContentForScreen": {
      "id": "6203d63f54a0bd82832288c5",
      "name": "sdfgsdfg",
      "entry": [
        {
          "entryVideos": [
            "6bb847e5-8b9a-477b-bfd1-68a109b3c707.mp4",
            "9b1628af-e69e-4d0e-9d53-b472a963a1ec.mp4",
            "830b0258-70f1-4206-b07b-fb60508e33c5.mp4"
          ]
        }
      ],
      "equipment": [
        {
          "id": "62025aa4237005069c569d63",
          "name": "dsfgsdfg",
          "position": null,
          "panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
          "productImage": "da245241-335e-4021-929c-d177a851c2ea.jpg"
        },
        {
          "id": "62025afa237005069c569d99",
          "name": "sdfgsdfgsdfgsdfgsdfgsdfgweqqwerwr",
          "position": null,
          "panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
          "productImage": "da245241-335e-4021-929c-d177a851c2ea.jpg"
        },
        {
          "id": "62025af4237005069c569d92",
          "name": "sdfgsdfgsdfgdsf",
          "position": null,
          "panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
          "productImage": "da245241-335e-4021-929c-d177a851c2ea.jpg"
        }
      ],
      "exercises": [
        {
          "equipment": "dsfgsdfg",
          "content": [
            {
              "id": "62025b27237005069c569dc0",
              "position": 1,
              "name": "sdfgsdfg",
              "level": "Intermediate",
              "panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
              "programVideo": "6bb847e5-8b9a-477b-bfd1-68a109b3c707.mp4",
              "instructions": [
                "sdfgsdfg",
                "sdfgsdfg",
                "sdfg"
              ]
            },
            {
              "id": "62025b30237005069c569dc7",
              "position": 2,
              "name": "sdfgsdfgsdfg",
              "level": "Intermediate",
              "panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
              "programVideo": "6bb847e5-8b9a-477b-bfd1-68a109b3c707.mp4",
              "instructions": [
                "sdfgsdfg",
                "sdfg",
                "hgfjgh"
              ]
            }
          ]
        },
        {
          "equipment": "sdfgsdfgsdfgdsf",
          "content": [
            {
              "id": "62025b80237005069c569e13",
              "position": 1,
              "name": "sdfg",
              "level": "Intermediate",
              "panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
              "programVideo": "6bb847e5-8b9a-477b-bfd1-68a109b3c707.mp4",
              "instructions": [
                "sdfg",
                "sdfgsdfg",
                "sdfgdf"
              ]
            }
          ]
        },
        {
          "equipment": "sdfgsdfgsdfgsdfgsdfgsdfgweqqwerwr",
          "content": [
            {
              "id": "62025b88237005069c569e1a",
              "position": 1,
              "name": "uitytyui",
              "level": "Intermediate",
              "panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
              "programVideo": "6bb847e5-8b9a-477b-bfd1-68a109b3c707.mp4",
              "instructions": [
                "ytuityui",
                "tyui",
                "tyuityuityui"
              ]
            }
          ]
        }
      ],
      "options": [
        {
          "bgImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
          "cards": []
        }
      ],
      "workouts": [],
      "files": []
    }
  }
}

As you can see, everything from "options" : [{"cards"}] is empty, but it shouldn't be, as there is the data in the database for it. What is even more interesting, is that when I console.log the contentOut object inside the last .map function (content.files.map()) I'm getting the full response.

Basically it looks like my resolver is returning the content before all of it is gathered.

If I add some if statement to check if all of my content is in the contentOut object, I'm getting empty response, just like the resolver couldn't be bothered to wait for all of the content...

Any ideas?

Many thanks in advance!



Solution 1:[1]

Ok, so after more Googling and fighting with it, I've re-write the whole code and use Promise.all for each part of the function in order to make sure that it will wait for the outcome of each await, before returning the value.

Now the code looks like this:

getMarsContentForScreen: async (_, { screen, token }, context) => {
  if (!context.screen) return {};
  console.log(screen, token);
  const contentOut = {};
  const screenExist = await MarsScreen.findOne({
    name: screen,
    token: token,
  });

  const getEntryVideos = async (content) => {
    let result = [{ entryVideos: [] }];
    await Asset.find({ _id: { $in: content } }, 'uri').then((response) =>
      response.map((val) => {
        result[0].entryVideos.push(val.uri.split('/').slice(-1)[0]);
      })
    );
    return result;
  };

  const getEquipment = async (content) => {
    let result = [];
    const ids = content.map((val) => {
      return val.id;
    });
    await MarsEquipment.find(
      { _id: { $in: ids } },
      'id name thumbnail background'
    ).then((response) =>
      response.map((val) => {
        result.push({
          id: val.id,
          name: val.name,
          panelImage: val.thumbnail.split('/').slice(-1)[0],
          productImage: val.background.split('/').slice(-1)[0],
        });
      })
    );
    return result;
  };

  const getExercises = async (content) => {
    let result = [];
    const ids = [].concat(
      ...content.map((val) => {
        result.push({
          equipment: val.equipment,
          content: [],
        });
        return val.content.map((valC) => {
          return valC.id;
        });
      })
    );
    await MarsExercise.find(
      { _id: { $in: ids } },
      'id name level text thumbnail video product'
    ).then((response) =>
      response.map((exer) => {
        let instructions = [];
        const index = result.indexOf(
          result.find((equip) => equip.equipment === exer.product)
        );
        for (const [key, value] of Object.entries(JSON.parse(exer.text))) {
          instructions.push(value);
        }
        result[index].content.push({
          id: exer.id,
          position: exer.position,
          name: exer.name,
          level: exer.level,
          instructions: instructions,
          panelImage: exer.thumbnail.split('/').slice(-1)[0],
          programVideo: exer.video.split('/').slice(-1)[0],
        });
      })
    );
    return result;
  };

  const getOptions = async (content) => {
    let result = content;
    const ids = content[0].cards.map((val) => {
      return val.panelImage;
    });
    await Asset.findById(content[0].bgImage, 'uri').then((response) => {
      result[0].bgImage = response.uri.split('/').slice(-1)[0];
    });
    await Asset.find({ _id: { $in: ids } }, 'id uri').then((response) =>
      response.map((val) => {
        let index = result[0].cards.indexOf(
          result[0].cards.find((card) => card.panelImage === val.id)
        );
        result[0].cards[index].panelImage = val.uri.split('/').slice(-1)[0];
      })
    );
    return result;
  };

  const getWorkouts = async (content) => {
    let result = content;
    const ids = content.map((val) => {
      return val.id;
    });
    await MarsWorkout.find(
      { _id: { $in: ids } },
      'id name thumbnail video text required'
    ).then((response) => {
      response.map((val) => {
        let index = result.indexOf(
          result.find((work) => work.id === val.id)
        );
        result[index].panelImage = val.thumbnail.split('/').slice(-1)[0];
        result[index].programVideo = val.video.split('/').slice(-1)[0];
      });
    });
    return result;
  };

  const getFiles = async (content) => {
    let result = [];
    await Asset.find({ _id: { $in: content } }, 'id uri updated_at').then(
      (response) => {
        response.map((val) => {
          result.push({
            id: val.id,
            uri: val.uri,
            filename: val.uri.split('/').slice(-1)[0],
            timestamp: val.updated_at,
          });
        });
      }
    );
    return result;
  };

  if (screenExist) {
    const content = await MarsContent.findOne({
      screens: { $in: screenExist.id },
    });
    if (content) {
      // ID
      contentOut.id = content.id;
      // NAME
      contentOut.name = content.name;
      // ENTRY
      const entry = getEntryVideos(content.entry);
      // EQUIPMENT
      const equipment = getEquipment(content.equipment);
      // EXERCISES
      const exercises = getExercises(content.exercises);
      // OPTIONS
      const options = getOptions(content.options);
      // WORKOUTS
      const workouts = getWorkouts(content.workouts);
      // FILES
      const files = getFiles(content.files);

      //  PROMISE
      const results = await Promise.all([
        entry,
        equipment,
        exercises,
        options,
        workouts,
        files,
      ]);
      //console.log(results);
      return {
        id: content.id,
        name: content.name,
        entry: results[0],
        equipment: results[1],
        exercises: results[2],
        options: results[3],
        workouts: results[4],
        files: results[5],
      };
    } else {
      return {};
    }
  }
},

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Kuba Pabis