J. Hesters
J. Hesters

Reputation: 14786

Conditionally send responses in an Express app

I'm curious whether you can write if statements in an Express app to conditionally execute your code without providing else statements.

if(pred) {
  doSomething()
}
return foo;
calcBar(); // doesn't run.

Above is the synchronous code that stops execution after the return statement.

My Express function looks like this:

app.get('/matches', async function(req, res) {
  try {
    const data = await someGraphQLCall();
    if(data.length === 0) {
      res.json({ message: "No data." });
    }
    const someOtherData = await someOtherGraphQLCall(data.foo);
    res.json({ someOtherData });
  } catch (err) {
    res.json({err})
  }
}

I know because of this question that code after the first res.json might still be executed. Is there a way to stop that? I don't want the second GraphQL call to execute if the first if condition is met. Is that possible without using else ?

Edit:

As the question I linked above mentioned, using a return statement is a bad option because:

it also makes it less meaningful and vague, cause it uses incorrect semantics. If you are not using the value from the function, then you shouldn't return one.

Upvotes: 3

Views: 8468

Answers (3)

Anand Undavia
Anand Undavia

Reputation: 3543

If you are wondering if there is a way to achieve that without the else, yes it is. But, It might not be THE cleanest way. IMO, using return is the best way to stop the execution of the controller.

Anyways, You can split the chunk of code into middlewares and use ternary operator to conditionally send responses.

In your example, separate out data = await someGraphQLCall(); as follows:

const middlewareOne = async function(req, res, next) {
    let data = [];
    let response = { message: "No data." };
    try {
        data = await someGraphQLCall();
        req.locals.data = data; // <- attach the data to req.locals
    } catch (err) {
        response = { err };
    }
    data.length === 0 ? res.json(response) : next();
};

And then, mount the middlewareOne BEFORE your controller:

app.get("/matches", middlewareOne, async function controller(req, res) {
    try {
        const someOtherData = await someOtherGraphQLCall(req.locals.data.foo);
        res.json({ someOtherData });
    } catch (err) {
        res.json({ err });
    }
});

How this works is, the controller function would only be executed by express if the next() is called from the previous middleware -- middlewareOne in the example.
And as middlewareOne only calls next() if the data.length is not 0, it would work as you expected.


For more information on passing data from one middleware to other, read this

Upvotes: 2

Oresztesz
Oresztesz

Reputation: 2430

You can use return keyword on the first response to immediately return from the function.

app.get('/matches', async function(req, res) {
  try {
    const data = await someGraphQLCall();
    if(data.length === 0) {
      return res.json({ message: "No data." });
    }
    const someOtherData = await someOtherGraphQLCall(data.foo);
    res.json({ someOtherData });
  } catch (err) {
    res.json({err})
  }
} 

Edit:

As an alternative, you can split the logic of the data and building up response. This way you can use return and it's easier to read:

app.get('/matches', async function (req, res) {
    try {
        const data = await getDataFromGraphQLCall();
        res.json(data);
    } catch (err) {
        res.json({ err })
    }
});

async function getDataFromGraphQLCall() {
    const data = await someGraphQLCall();
    if (data.length === 0) {
        return { message: "No data." };
    }
    const someOtherData = await someOtherGraphQLCall(data.foo);
    return { someOtherData };
}

Upvotes: 7

Akinjide
Akinjide

Reputation: 2753

The return statement terminates the function execution in this context. In my opinion, you should handle the success case then the error case since the code will be read top to bottom.

In if statement, data could be undefined or null.

You can read more here: MDN - return

app.get('/matches', async function(req, res) {
  try {
    const data = await someGraphQLCall();

    // alternative, if (data && data[0]) {
    if (data && data.length) {
      const someOtherData = await someOtherGraphQLCall(data.foo);
      return res.json({ someOtherData });
    }

    return res.json({ message: "No data." });
  } catch (err) {
    console.log(err); // log error with logger and drain to loggly.
    res.json({ err })
  }
} 

With Void operator:

Void operator allows you to return undefined but evaluate the given expression.

You can read more here: MDN - Void

app.get('/matches', async function(req, res) {
  try {
    const data = await someGraphQLCall();

    // alternative, if (data && data[0]) {
    if (data && data.length) {
      const someOtherData = await someOtherGraphQLCall(data.foo);
      return void res.json({ someOtherData });
    }

    return void res.json({ message: "No data." });
  } catch (err) {
    console.log(err); // log error with logger and drain to loggly.
    res.json({ err })
  }
}

Upvotes: 0

Related Questions