Skip to content

Commit

Permalink
revert(router): Focus middleware API (remove done callback syntax and…
Browse files Browse the repository at this point in the history
… generator middleware support)

BREAKING CHANGE: Removed generator middleware API and `done` callback support for async in
middleware and beforeNavigateCallbacks
  • Loading branch information
caseyWebb committed Jun 28, 2018
1 parent ccc6b74 commit 800625c
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 146 deletions.
129 changes: 56 additions & 73 deletions packages/router/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import 'core-js/es7/symbol'
import * as ko from 'knockout'
import { IContext } from './'
import { Route } from './route'
import { Router, Middleware } from './router'
import {
Callback,
isThenable,
castLifecycleObjectMiddlewareToGenerator,
sequence
} from './utils'
import { Router, Middleware, Lifecycle } from './router'
import { MaybePromise } from './utils'

export class Context /* implements IContext, use Context & IContext */ {
public $child: Context & IContext
Expand All @@ -22,10 +16,12 @@ export class Context /* implements IContext, use Context & IContext */ {
with?: { [prop: string]: any }
}

private readonly _beforeNavigateCallbacks: (() => MaybePromise<
void | false
>)[] = []
private _queue: Promise<void>[] = []
private _beforeNavigateCallbacks: Callback<void>[] = []
private _appMiddlewareDownstream: Callback<void>[] = []
private _routeMiddlewareDownstream: Callback<void>[] = []
private _appMiddlewareLifecycles: Lifecycle[] = []
private _routeMiddlewareLifecycles: Lifecycle[] = []

constructor(
public router: Router,
Expand Down Expand Up @@ -55,7 +51,7 @@ export class Context /* implements IContext, use Context & IContext */ {
}
}

public addBeforeNavigateCallback(cb: Callback<void>) {
public addBeforeNavigateCallback(cb: () => MaybePromise<false | void>) {
this._beforeNavigateCallbacks.unshift(cb)
}

Expand Down Expand Up @@ -119,14 +115,19 @@ export class Context /* implements IContext, use Context & IContext */ {
}

public async runBeforeNavigateCallbacks(): Promise<boolean> {
let ctx: Context & IContext = this
let callbacks: Callback<boolean | void>[] = []
let ctx: void | Context & IContext = this as any
let callbacks: (() => MaybePromise<boolean | void>)[] = []
while (ctx) {
callbacks = [...ctx._beforeNavigateCallbacks, ...callbacks]
ctx = ctx.$child
}
const { success } = await sequence(callbacks)
return success
for (const cb of callbacks) {
const ret = await cb()
if (ret === false) {
return false
}
}
return true
}

public render() {
Expand All @@ -141,31 +142,15 @@ export class Context /* implements IContext, use Context & IContext */ {
}

public async runBeforeRender(flush = true) {
const appMiddlewareDownstream = Context.runMiddleware(
const ctx: Context & IContext = this as any
this._appMiddlewareLifecycles = await Context.startLifecycle(
Router.middleware,
this
ctx
)
const routeMiddlewareDownstream = Context.runMiddleware(
this._routeMiddlewareLifecycles = await Context.startLifecycle(
this.route.middleware,
this
)

const { count: numAppMiddlewareRanPreRedirect } = await sequence(
appMiddlewareDownstream
)
const { count: numRouteMiddlewareRanPreRedirect } = await sequence(
routeMiddlewareDownstream
)

this._appMiddlewareDownstream = appMiddlewareDownstream.slice(
0,
numAppMiddlewareRanPreRedirect
)
this._routeMiddlewareDownstream = routeMiddlewareDownstream.slice(
0,
numRouteMiddlewareRanPreRedirect
ctx
)

if (this.$child && typeof this._redirect === 'undefined') {
await this.$child.runBeforeRender(false)
}
Expand All @@ -175,21 +160,25 @@ export class Context /* implements IContext, use Context & IContext */ {
}

public async runAfterRender() {
await sequence([
...this._appMiddlewareDownstream,
...this._routeMiddlewareDownstream
])
for (const l of [
...this._appMiddlewareLifecycles,
...this._routeMiddlewareLifecycles
]) {
if (l.afterRender) await l.afterRender()
}
await this.flushQueue()
}

public async runBeforeDispose(flush = true) {
if (this.$child && typeof this._redirect === 'undefined') {
await this.$child.runBeforeDispose(false)
}
await sequence([
...this._routeMiddlewareDownstream,
...this._appMiddlewareDownstream
])
for (const l of [
...this._routeMiddlewareLifecycles,
...this._appMiddlewareLifecycles
]) {
if (l.beforeDispose) await l.beforeDispose()
}
if (flush) {
await this.flushQueue()
}
Expand All @@ -199,10 +188,12 @@ export class Context /* implements IContext, use Context & IContext */ {
if (this.$child && typeof this._redirect === 'undefined') {
await this.$child.runAfterDispose(false)
}
await sequence([
...this._routeMiddlewareDownstream,
...this._appMiddlewareDownstream
])
for (const l of [
...this._routeMiddlewareLifecycles,
...this._appMiddlewareLifecycles
]) {
if (l.afterDispose) await l.afterDispose()
}
if (flush) {
await this.flushQueue()
}
Expand All @@ -216,31 +207,23 @@ export class Context /* implements IContext, use Context & IContext */ {
await Promise.all<Promise<void>>([thisQueue, ...childQueues])
}

private static runMiddleware(
private static async startLifecycle(
middleware: Middleware[],
ctx: Context
): Callback<void>[] {
return middleware.map((fn) => {
const runner = castLifecycleObjectMiddlewareToGenerator(fn)(ctx)
let beforeRender = true
return async () => {
const ret = runner.next()
if (isThenable(ret)) {
await ret
} else if (
isThenable((ret as IteratorResult<Promise<void> | void>).value)
) {
await (ret as IteratorResult<Promise<void> | void>).value
}
if (beforeRender) {
// this should only block the sequence for the first call,
// and allow cleanup after the redirect
beforeRender = false
return typeof ctx._redirect === 'undefined'
} else {
return true
}
ctx: Context & IContext
): Promise<Lifecycle[]> {
const downstream: Lifecycle[] = []

for (const fn of middleware) {
if (typeof ctx._redirect !== 'undefined') {
break
}
})
const lifecycle = await fn(ctx)
if (lifecycle) {
if (lifecycle.beforeRender) await lifecycle.beforeRender()
downstream.push(lifecycle)
}
}

return downstream
}
}
29 changes: 10 additions & 19 deletions packages/router/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as ko from 'knockout'
import { IContext } from './'
import { Context } from './context'
import { RoutePlugin, Route, RouteMap } from './route'
import { Callback, MaybePromise, traversePath, log } from './utils'
import { castArray, MaybePromise, traversePath, log } from './utils'

export type RouterConfig = {
base?: string
Expand All @@ -24,27 +24,18 @@ export type SimpleMiddleware =
| ((ctx: Context & IContext) => MaybePromise<void>)
| ((ctx: Context & IContext, done?: () => void) => void)

export type LifecycleObjectMiddleware = (
export type LifecycleMiddleware = (
ctx: Context & IContext
) => {
beforeRender?: Callback<void>
afterRender?: Callback<void>
beforeDispose?: Callback<void>
afterDispose?: Callback<void>
}

export type LifecycleGeneratorMiddleware = (
ctx: Context & IContext
) => // sync generators yielding nothing or a promise
) => MaybePromise<Lifecycle>

| IterableIterator<void | Promise<void>>
// async generators (async/await in block, but yield nothing)
| AsyncIterableIterator<void>
export type Middleware = SimpleMiddleware | LifecycleMiddleware

export type Middleware =
| SimpleMiddleware
| LifecycleObjectMiddleware
| LifecycleGeneratorMiddleware
export type Lifecycle = {
beforeRender?(): MaybePromise<void>
afterRender?(): MaybePromise<void>
beforeDispose?(): MaybePromise<void>
afterDispose?(): MaybePromise<void>
}

export class Router {
public static head: Router
Expand Down
69 changes: 15 additions & 54 deletions packages/router/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,64 +104,25 @@ export function isActivePath({
return true
}

export function isThenable(x: any) {
return !isUndefined(x) && isFunction(x.then)
}

export function promisify(
_fn: (...args: any[]) => void = noop
): (...args: any[]) => Promise<any> {
return async (...args) => {
const fn = () =>
_fn.length === args.length + 1
? new Promise((r) => {
_fn(...args, r)
})
: _fn(...args)

const ret = fn()

return isThenable(ret) ? await ret : ret
}
}

export function castLifecycleObjectMiddlewareToGenerator(
fn: Middleware
): LifecycleGeneratorMiddleware {
return async function*(ctx: Context & IContext) {
const ret = await promisify(fn)(ctx)
if (ret && isFunction(ret.next)) {
for await (const v of ret) {
yield await v
}
} else if (isPlainObject(ret)) {
yield await promisify(ret.beforeRender)()
yield await promisify(ret.afterRender)()
yield await promisify(ret.beforeDispose)()
yield await promisify(ret.afterDispose)()
} else {
yield ret
}
}
}

export function getRouterForBindingContext(bindingCtx: KnockoutBindingContext) {
while (!isUndefined(bindingCtx)) {
if (!isUndefined(bindingCtx.$router)) {
export function getRouterForBindingContext(
bindingCtx: ko.BindingContext
): Router {
while (true) {
if (bindingCtx.$router) {
return bindingCtx.$router
} else if (!bindingCtx.$parentContext) {
return Router.head
} else {
bindingCtx = bindingCtx.$parentContext as ko.BindingContext
}
bindingCtx = bindingCtx.$parentContext
}
return Router.head
}

// tslint:disable-next-line no-console
const _consoleLogger: any = console
const _logger = (t: string) => (...ms: string[]) =>
_consoleLogger[t]('[@profiscience/knockout-contrib-router]', ...ms)
export const log = {
error(...messages: string[]) {
// tslint:disable-next-line no-console
console.error('[@profiscience/knockout-contrib-router]', ...messages)
},
warn(...messages: string[]) {
// tslint:disable-next-line no-console
console.warn('[@profiscience/knockout-contrib-router]', ...messages)
}
error: _logger('error'),
warn: _logger('warn')
}

0 comments on commit 800625c

Please sign in to comment.