You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GraphQL中能否在Mutation解析器内调用其他查询/突变?

Refactoring Your addSet Resolver: Clean Solutions for Complex Logic

Great question! Handling this kind of multi-step mutation logic in a single resolver can get messy fast—let’s break down some better approaches to keep your code clean and maintainable.

1. Split Logic into Reusable Helper Functions

The simplest win is to extract repetitive or independent operations into standalone helper functions. Since GraphQL resolvers are just JavaScript functions, you can call these helpers directly inside addSet to keep the resolver focused on orchestration rather than doing all the heavy lifting.

Here’s how you could refactor your current code:

First, create helper functions for your core operations:

// Helper to upsert an exercise (update if exists, create if not)
async function upsertExercise(exerciseName, username, reps, weight, notes) {
  const normalizedExerciseName = exerciseName.toLowerCase();
  let exercise = await Exercise.findOne({ exerciseName: normalizedExerciseName, username });
  
  if (exercise) {
    exercise.sets.unshift({ reps, weight, username, createdAt: Date.now(), notes });
  } else {
    exercise = new Exercise({
      exerciseName: normalizedExerciseName,
      username,
      sets: [{ reps, weight, username, createdAt: Date.now(), notes }]
    });
  }
  
  return exercise.save();
}

// Helper to handle active workout logic
async function updateActiveWorkout(username, exerciseName, reps, weight, notes) {
  // Get the latest workout for the user
  const latestWorkout = await Workout.findOne({ username }).sort({ createdAt: -1 });
  const now = Date.now();
  const fourHoursInMs = 4 * 60 * 60 * 1000; // 14400000ms
  
  // Case 1: No existing workout, or latest workout is older than 4 hours
  if (!latestWorkout || now > latestWorkout.createdAt + fourHoursInMs) {
    // Create new workout and add the exercise
    const newExercise = await upsertExercise(exerciseName, username, reps, weight, notes);
    const newWorkout = new Workout({
      workoutName: `Session ${new Date().toLocaleString()}`, // Let users customize this later if needed
      username,
      createdAt: Date.now(),
      exercises: [newExercise]
    });
    return newWorkout.save();
  }
  
  // Case 2: Active workout exists
  const normalizedExerciseName = exerciseName.toLowerCase();
  const exerciseInWorkout = latestWorkout.exercises.find(ex => 
    ex.exerciseName.toLowerCase() === normalizedExerciseName
  );
  
  if (exerciseInWorkout) {
    // Update existing exercise in workout
    exerciseInWorkout.sets.unshift({ reps, weight, username, createdAt: Date.now(), notes });
  } else {
    // Add new exercise to workout
    const newExercise = await upsertExercise(exerciseName, username, reps, weight, notes);
    latestWorkout.exercises.push(newExercise);
  }
  
  return latestWorkout.save();
}

Then simplify your addSet resolver:

async addSet(_, { exerciseName, reps, weight, notes }, context) {
  const user = checkAuth(context);
  
  // Input validation
  if (!exerciseName.trim()) {
    throw new UserInputError('Empty Field', {
      errors: { body: 'Choose an exercise' }
    });
  }
  if (isNaN(reps) || reps === null || isNaN(weight) || weight === null) {
    throw new UserInputError('Invalid Fields', {
      errors: {
        reps: 'Enter a valid number of reps',
        weight: 'Enter a valid weight amount'
      }
    });
  }
  
  // Delegate to helpers
  await upsertExercise(exerciseName, user.username, reps, weight, notes);
  await updateActiveWorkout(user.username, exerciseName, reps, weight, notes);
  
  // Return data the client needs (e.g., the new set details)
  return { reps, weight, createdAt: Date.now(), notes };
}

2. Can You Call Other Resolvers Directly?

Short answer: Yes! Resolvers are just functions, so if you have existing query/mutation resolvers (like createExercise or updateWorkout), you can import them and call them like any other function. Just make sure you pass the correct arguments (parent, args, context, info—you can usually pass null for parent and info if you don’t need them).

Example:

// Import your existing resolvers
const { createExercise, updateWorkout } = require('./otherResolvers');

async addSet(_, args, context) {
  const user = checkAuth(context);
  
  // ... input validation ...
  
  // Call createExercise resolver (adjust args to match its expected shape)
  const exercise = await createExercise(null, {
    exerciseName: args.exerciseName.toLowerCase(),
    username: user.username,
    sets: [{ reps: args.reps, weight: args.weight, notes: args.notes }]
  }, context);
  
  // Call updateWorkout resolver similarly
  // ...
}

That said, this approach is less ideal than using helper functions or a service layer. Resolvers are meant to handle GraphQL-specific logic (like parsing args, handling context), not core business logic. Reusing them can lead to tight coupling and unexpected side effects.

3. Use a Service Layer (Best for Scalability)

For larger apps, you’ll want to move your business logic into a dedicated service layer. This separates your GraphQL API layer from your core application logic, making it easier to test, reuse, and maintain.

Example of a service layer setup:

// services/exerciseService.js
class ExerciseService {
  static async upsertExercise(exerciseName, username, setData) {
    const normalizedExerciseName = exerciseName.toLowerCase();
    let exercise = await Exercise.findOne({ exerciseName: normalizedExerciseName, username });
    
    if (exercise) {
      exercise.sets.unshift({ ...setData, username, createdAt: Date.now() });
    } else {
      exercise = new Exercise({
        exerciseName: normalizedExerciseName,
        username,
        sets: [{ ...setData, username, createdAt: Date.now() }]
      });
    }
    
    return exercise.save();
  }
}

// services/workoutService.js
class WorkoutService {
  static async updateActiveWorkout(username, exerciseName, setData) {
    const latestWorkout = await Workout.findOne({ username }).sort({ createdAt: -1 });
    const now = Date.now();
    const fourHoursInMs = 4 * 60 * 60 * 1000;
    
    if (!latestWorkout || now > latestWorkout.createdAt + fourHoursInMs) {
      const newExercise = await ExerciseService.upsertExercise(exerciseName, username, setData);
      const newWorkout = new Workout({
        workoutName: `Session ${new Date().toLocaleString()}`,
        username,
        createdAt: Date.now(),
        exercises: [newExercise]
      });
      return newWorkout.save();
    }
    
    const normalizedExerciseName = exerciseName.toLowerCase();
    const exerciseInWorkout = latestWorkout.exercises.find(ex => 
      ex.exerciseName.toLowerCase() === normalizedExerciseName
    );
    
    if (exerciseInWorkout) {
      exerciseInWorkout.sets.unshift({ ...setData, username, createdAt: Date.now() });
    } else {
      const newExercise = await ExerciseService.upsertExercise(exerciseName, username, setData);
      latestWorkout.exercises.push(newExercise);
    }
    
    return latestWorkout.save();
  }
}

Then in your resolver:

const { ExerciseService, WorkoutService } = require('./services');

async addSet(_, args, context) {
  const user = checkAuth(context);
  const setData = { reps: args.reps, weight: args.weight, notes: args.notes };
  
  // ... validation ...
  
  await ExerciseService.upsertExercise(args.exerciseName, user.username, setData);
  await WorkoutService.updateActiveWorkout(user.username, args.exerciseName, setData);
  
  return { ...setData, createdAt: Date.now() };
}

Quick Fixes for Your Current Code

While refactoring, don’t forget these small bugs in your original code:

  • const workoutExists = await Workout.findOne({ username: username }) → username is undefined here—use user.username instead.
  • You need to handle the case where workoutExists is null (user has no prior workouts).
  • In your weight validation, you have reps === null instead of weight === null.

内容的提问来源于stack exchange,提问作者JLI_98

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:25:39