Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

return values of next() #2632

Closed
wants to merge 1 commit into from
Closed

Conversation

refractalize
Copy link

I use promises a lot in my express apps, and I was curious about how to handle errors from routes that used promises in a more general way. What I wanted to do was install some middleware that could handle any error from any route below it, whether it was a normal error thrown or an asynchronous error in the form of a rejected promise.

This is what I wanted to do:

function handleErrors(req, res, next) {
  function sendError(error) {
    console.log(error);
    res.status(500).send({message: error.message});
  }

  var result;
  try {
    result = next();
  } catch (error) {
    sendError(error);
  }

  if (result && typeof result.then === 'function') {
    result.then(undefined, sendError);
  }
}

app.use(handleErrors);

// one of many routes
app.get('/thing', function (req, res) {
  return mongoDb.find({_id: 'thing'}).exec().then(function (thing) {
    res.send(thing);
  });
});

Now, if something happens to the mongo DB, the /thing route will NOT res.send() anything, simply return the promise to the error handling middleware, which will detect the error and all is (kinda) ok. At least the server returns something.

Problem is that express and connect don't really think about returning the result of next() functions. This is, on the surface at least, fairly easy to fix. I've made a start in the files below. But the connect API is very well understood already, and while this is not a breaking change, it is a very subtle extension to the API.

I've started putting return statements into express in the parts that matter, just to get this example working. I haven't finished yet, I intend to write tests and explore the wider scope of this change.

Is appetite for this?

(and I really don't mean to start an argument about promises vs callbacks vs generators)

@felixfbecker
Copy link

If express 5 supports promises this will be added earlier or later as next() will need to return a promise.

@refractalize
Copy link
Author

Cheers, happy to finish this off when I get a nod. What are the ideas for express 5 and promises, anything you can point me to?

@felixfbecker
Copy link

#2237 #2259 #2763 pillarjs/router#25
But I think there hasn't been any discussion yet about return values of promises.

@dougwilson dougwilson self-assigned this Nov 2, 2015
@dougwilson
Copy link
Contributor

So, if this PR is purely a way to get Promises through in Express, I think the effort belongs in other initiatives, rather than trying to get return values from next passed up. Saying that next() can return a value from an upper middleware will be a large community effort, so needs to be carefully thought about (there are many, many middleware-wrapping patterns that do not pass up return values people will continue to copy-and-paste for years to come, as well as just all modules wrapping will have to deal with).

On the surface, it seems reasonable, but unfortunately I'm going to have to decline this after some behind the scenes talking. The reason is that next() is just a callback, and callbacks do not return a value for a very good reason: they are executed after the current stack has unraveled:

app.use(function (req, res, next) {
  // do things
  setImmediate(function () { // an async thing
    next() // impossible to return any result here
  })
})

Your pattern above will break down as soon as you use just one async middleware from npm between app.use(handleErrors); and your app.get handler, as they will not propagate any values, and they may never, if the author doesn't want to use promises in their library.

In a future promise Express (expected to be 5.0), your given code below will work as you expect:

app.get('/thing', function (req, res) {
  return mongoDb.find({_id: 'thing'}).exec().then(function (thing) {
    res.send(thing);
  });
});

Your handleErrors may not, but that would probably warrant a different discussion, around how promises should interact with prior middlewares and if next() should just always return a promise, even if a later middleware does not use promises (i.e. should Express force everything into a promise?).

@dougwilson dougwilson closed this Nov 2, 2015
@felixfbecker
Copy link

Good reasoning @dougwilson. I initally thought by return value he meant a callback return value, like calling next(err) for an error and next(null, value) for a value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants