Skip to content
This repository has been archived by the owner on Dec 19, 2017. It is now read-only.

Commit

Permalink
Better UX w/ async middleware
Browse files Browse the repository at this point in the history
Prevent intermediate blank page while waiting for
asychronous before render middleware
  • Loading branch information
caseyWebb committed Dec 29, 2016
1 parent 1ef3214 commit 4e2792e
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 56 deletions.
31 changes: 20 additions & 11 deletions docs/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,17 +192,26 @@ I :heart: future JS.

## Execution Order

Middleware is executed in the following order...

- App before render
- Route before render
- App after render
- Router after render
- [before navigate callbacks]
- Route before dispose
- App before dispose
- Route after dispose
- App after dispose
Assuming navigation from a => b, where "X/app" indicates app middleware for route X,
middleware is executed in the following order...

- a: before dispose
- a/app: before dispose

- b/app: before render
- b: before render

- a: after dispose
- a/app: after dispose

- b/app: after render
- b: after render

*Why is the next page's before render middleware called before this one is disposed
entirely!?*

Because doing prevents intermediate whitespace while asynchronous before render
middleware is executing.

---

Expand Down
71 changes: 34 additions & 37 deletions src/route.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import ko from 'knockout'
import pathtoRegexp from 'path-to-regexp'
import Router from './router'
import { flatMap, isArray, isFunction, isPlainObject, isString, runMiddleware, sequence } from './utils'
Expand Down Expand Up @@ -75,46 +74,44 @@ export default class Route {
return [params, path.replace(new RegExp(childPath + '$'), ''), childPath]
}

async run(ctx) {
let disposals = []
this.dispose = async () => {
async runBeforeRender(ctx) {
const afterRenders = []
const beforeDisposes = []
const afterDisposes = []

this.runAfterRender = async () => {
if (ctx.$child) {
await ctx.$child.route.dispose()
await ctx.$child.route.runAfterRender()
}
return await sequence(disposals)
return await sequence(afterRenders)
}

const [appUpstream, appNext] = runMiddleware(Router.middleware, ctx)
disposals = [
// before dispose
appNext,
// dispose
appNext
]
await appUpstream

const [routeUpstream, routeNext] = runMiddleware(this.middleware, ctx)
disposals = [
// before dispose
routeNext,
appNext,
() => {
ctx.router.component(false)
ko.tasks.runEarly()
},
// after dispose
routeNext,
appNext
]
await routeUpstream

if (ctx.route.component) {
ctx.router.component(ctx.route.component)
ko.tasks.runEarly()
this.runBeforeDispose = async () => {
if (ctx.$child) {
await ctx.$child.route.runBeforeDispose()
}
return await sequence(beforeDisposes)
}
this.runAfterDispose = async () => {
if (ctx.$child) {
await ctx.$child.route.runAfterDispose()
}
return await sequence(afterDisposes)
}

// after render
await appNext()
await routeNext()
const [appBeforeRender, appDownstream] = runMiddleware(Router.middleware, ctx)

afterRenders.push(appDownstream)
beforeDisposes.push(appDownstream)
afterDisposes.push(appDownstream)

await appBeforeRender

const [routeBeforeRender, routeDownstream] = runMiddleware(this.middleware, ctx)

afterRenders.push(routeDownstream)
beforeDisposes.unshift(routeDownstream)
afterDisposes.unshift(routeDownstream)

await routeBeforeRender
}
}
31 changes: 23 additions & 8 deletions src/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class Router {
}

async update(url, args) {
const fromCtx = this.ctx

if (isBool(args)) {
args = { push: args }
} else if (isUndefined(args)) {
Expand All @@ -57,23 +59,21 @@ class Router {

const [params, pathname, childPath] = route.parse(path)

if (this.ctx && this.ctx.pathname === pathname && !args.force) {
if (fromCtx && fromCtx.pathname === pathname && !args.force) {
if (this.$child) {
return await this.$child.update(childPath, args.push)
} else {
return false
}
}

if (this.ctx) {
const shouldNavigate = await this.ctx.runBeforeNavigateCallbacks()
if (fromCtx) {
const shouldNavigate = await fromCtx.runBeforeNavigateCallbacks()
if (shouldNavigate === false) {
return false
}

this.isNavigating(true)

await this.ctx.route.dispose()
}

const currentUrl = Router.canonicalizePath(location.pathname + location.search + location.hash)
Expand All @@ -85,15 +85,30 @@ class Router {
this.base + path + search + hash
)

this.ctx = new Context(Object.assign({}, this.passthrough, {
const toCtx = new Context(Object.assign({}, this.passthrough, {
router: this,
params,
route,
path,
pathname
}))

await route.run(this.ctx)
if (fromCtx) {
await fromCtx.route.runBeforeDispose()
}

await toCtx.route.runBeforeRender(toCtx)

this.ctx = toCtx
this.component(false)
ko.tasks.runEarly()
this.component(this.ctx.route.component)
ko.tasks.runEarly()

if (fromCtx) {
await fromCtx.route.runAfterDispose()
}
await toCtx.route.runAfterRender()

this.isNavigating(false)

Expand Down Expand Up @@ -130,7 +145,7 @@ class Router {
window.removeEventListener(events.popstate, this.onpopstate, false)
Router.unlink(this)
if (this.isRoot) {
this.ctx.route.dispose()
this.ctx.route.runBeforeDispose().then(() => this.ctx.route.runAfterDispose())
}
}

Expand Down

0 comments on commit 4e2792e

Please sign in to comment.