Skip to content

Resolve paths for views asynchronously#2653

Open
aredridel wants to merge 19 commits into
expressjs:masterfrom
aredridel:async-view-resolve
Open

Resolve paths for views asynchronously#2653
aredridel wants to merge 19 commits into
expressjs:masterfrom
aredridel:async-view-resolve

Conversation

@aredridel

Copy link
Copy Markdown
Contributor

This eliminates the only post-startup synchronous I/O in Express.

In addition, this makes the view.lookup method non-private, intended to be used
by view engines to resolve paths to partials, and, if the View class is
extended, could be used for other resources such as internationalized messages.

@dougwilson

Copy link
Copy Markdown
Contributor

Nice! Anyway we can add to this a way to prevent stampeding (which the sync version protected from) to the default lookup implementation?

@aredridel

Copy link
Copy Markdown
Contributor Author

Oh, yes! I think I could work that in.

@aredridel

Copy link
Copy Markdown
Contributor Author

Updated.

@aredridel aredridel force-pushed the async-view-resolve branch 4 times, most recently from a048a24 to 4de612f Compare May 19, 2015 21:28
@dougwilson dougwilson self-assigned this May 20, 2015
@dougwilson dougwilson force-pushed the master branch 2 times, most recently from 5f268a4 to 9848645 Compare July 31, 2015 20:58
@gabrielcsapo

Copy link
Copy Markdown

👍

@bjohansebas

Copy link
Copy Markdown
Member

Hey, @aredridel could you rebase this PR, please?

@bjohansebas bjohansebas removed the pr label Mar 27, 2025
@bjohansebas

Copy link
Copy Markdown
Member

@expressjs/express-collaborators @wesleytodd @UlisesGascon Any objections to releasing this in the next minor version of Express?

@jonchurch

Copy link
Copy Markdown
Member

Does someone want to own last mile on this? Its in my list of stuck PRs, so either working to understand and land, or understand and close
cc @expressjs/express-collaborators

Im unassigning @blakeembrey bc its been so long idk why I assigned it to him and I doubt he does either

the (current) test fails are unrelated to the PR

@bjohansebas

Copy link
Copy Markdown
Member

It’s been a long time since the last time I reviewed it, I’d like to take another look in case I missed something. I’ll put it on my TODO list.

@bjohansebas bjohansebas self-assigned this Jan 5, 2026
@bjohansebas bjohansebas added the semver-minor This change is a semver minor label Jan 17, 2026
@bjohansebas

Copy link
Copy Markdown
Member

A few things I changed were:

  • Removed lookupMain and restored app.render to its simpler form.
    The original PR introduced a View.prototype.lookupMain(cb) method that app.render had to call explicitly, with an if (!view.path) { lookupMain(...) } branch. I moved that logic into View.prototype.render instead (lazy lookup on the first render). This lets app.render go back to not knowing anything about how views are resolved, reduces the internal API surface, and means custom View implementations don't need to implement a new method.

  • Treat all stat errors as cache misses.
    The original implementation aborted rendering for any stat error other than ENOENT (for example, ENOTDIR if one of the view roots points to a file). I restored the synchronous behavior, where any stat error is treated as "not found" and the search simply continues.

  • Preserved the custom View contract and error behavior.
    Since the original PR always called lookupMain, it changed the contract for app.set('view', ...). I kept it identical to the synchronous implementation, so existing custom View classes continue to work unchanged.

On top of that, I added a couple of improvements beyond the original implementation:

  • Coalesced concurrent renders.
    The original implementation didn't do this. If N concurrent renders requested the same cold view, it would perform N independent lookups. I collapsed them into a single lookup, which can reduce I/O by up to 50× during bursts.

  • Handled invalid view roots without introducing new failures.
    The original implementation didn't handle non-string entries in the views array. I restored the same TypeError behavior as the synchronous implementation instead of introducing a different failure mode.

Overall, the goal of these changes was to avoid introducing breaking changes while still improving the implementation.

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

Labels

5.x enhancement semver-minor This change is a semver minor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants