Skip to content
This repository has been archived by the owner on Mar 21, 2019. It is now read-only.

Commit

Permalink
extract runRouteHandlers to routeUtils
Browse files Browse the repository at this point in the history
  • Loading branch information
michalkvasnicak committed Jan 4, 2016
1 parent 902a698 commit 26f2733
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 67 deletions.
76 changes: 9 additions & 67 deletions src/Router.js
Original file line number Diff line number Diff line change
@@ -1,30 1,11 @@
import Route from './Route';
import { normalizeRouteDefinition } from './utils/routeUtils';
import { normalizeRouteDefinition, runRouteHandlers } from './utils/routeUtils';
import { resolveWithFirstMatched } from './utils/routerUtils';
import { Actions } from 'history';
import invariant from 'invariant';
import { RouteNotFoundError } from './errors';
import { createHref, parseQuery } from './utils/urlUtils';

/**
* Reduce promises
*
* @param {Function} fn
* @param {*} start
* @returns {Function}
*/
function reduce(fn, start) {
return (val) => {
const values = Array.isArray(val) ? val : [val];

return values.reduce((promise, curr) => {
return promise.then((prev) => {
return fn(prev, curr);
});
}, Promise.resolve(start));
};
}

function instantiateRoutes(routes) {
return routes.map((definition) => {
const normalized = normalizeRouteDefinition(definition);
Expand Down Expand Up @@ -96,7 77,6 @@ export default class Router {
resolveWithFirstMatched(this.routes, location.pathname, parseQuery(location.search)).then(
(newRoute) => {
this._currentRoute = newRoute;
this._callEventListeners('changeSuccess', newRoute);

// replace state with new route if is not set (initial load)
if (!location.state) {
Expand All @@ -106,6 86,8 @@ export default class Router {
);
}

this._callEventListeners('changeSuccess', newRoute);

// do nothing about state because it is already store
this.onTransition(null, newRoute);
},
Expand Down Expand Up @@ -193,49 175,6 @@ export default class Router {
* @returns {Promise}
*/
run(path, query = {}) {
// runs route handler bound to given arguments (from our code)
// wrapper can call it with additional parameters
const runWrappedHandler = (originalHandler, originalProps, wrapper) => {
return wrapper(
originalHandler.bind(this, ...originalProps)
);
};

const runRouteHandlers = (handlers, route, ...args) => {
return new Promise((_resolve, _reject) => {
// resolve if current route is not defined (initial load for onLeave?)
if (!route) {
return _resolve();
}

// if running onEnter, run them from parent to child
// if onLeave, run them from child to parent

// run
return reduce(
(acc, current) => {
try {
const result = runWrappedHandler(current, args, this.handlerWrappers[handlers]);

if (result && typeof result.then === 'function') {
return result.then(res => {
acc.push(res);

return acc;
});
}

acc.push(result);

return Promise.resolve(acc);
} catch (e) {
return Promise.reject(e);
}
}, []
)(handlers === 'onEnter' ? route[handlers] : route[handlers].reverse()).then(_resolve, _reject);
});
};

const rejectTransition = (reason) => {
const err = new Error(reason);

Expand Down Expand Up @@ -288,11 227,14 @@ export default class Router {
};

const runResolvedRoute = (resolvedRoute) => {
this._callEventListeners('changeStart', this._currentRoute, resolvedRoute, this);
const currentRoute = this._currentRoute;
this._callEventListeners('changeStart', currentRoute, resolvedRoute, this);

const handlerWrappers = this.handlerWrappers;

// call on leave in order (so we can cancel transition)
return runRouteHandlers('onLeave', this._currentRoute, resolvedRoute, this).then(
() => runRouteHandlers('onEnter', resolvedRoute, this._currentRoute, resolvedRoute, this).then(
return runRouteHandlers('onLeave', currentRoute, handlerWrappers, resolvedRoute, this).then(
() => runRouteHandlers('onEnter', resolvedRoute, handlerWrappers, currentRoute, resolvedRoute, this).then(
() => resolveComponents(resolvedRoute.components).then(
(components) => {
return finishRun({ ...resolvedRoute, components });
Expand Down
66 changes: 66 additions & 0 deletions src/utils/routeUtils.js
Original file line number Diff line number Diff line change
@@ -1,6 1,25 @@
import { normalizeSlashes, trimSlashesFromPathEnd } from './stringUtils';
import invariant from 'invariant';

/**
* Reduce promises
*
* @param {Function} fn
* @param {*} start
* @returns {Function}
*/
function reduce(fn, start) {
return (val) => {
const values = Array.isArray(val) ? val : [val];

return values.reduce((promise, curr) => {
return promise.then((prev) => {
return fn(prev, curr);
});
}, Promise.resolve(start));
};
}

/**
* Builds path matcher
*
Expand Down Expand Up @@ -112,3 131,50 @@ export function normalizeRouteDefinition(definition) {
component: definition.component || null
};
}

/* eslint-disable consistent-return */
export function runRouteHandlers(handlers, route, wrappers = [], ...args) {
// if current route is not defined, resolve immediately
// this will prevent calling onLeave on initial load, because we don't have previous route
if (!route) {
return Promise.resolve();
}

// runs route handler bound to given arguments (from our code)
// wrapper can call it with additional parameters
const runWrappedHandler = (originalHandler, originalProps, wrapper) => {
return wrapper((...fromWrapper) => originalHandler(...originalProps, ...fromWrapper));
};

// create handlers runner
const composedHandlers = reduce(
(acc, current) => {
try {
const result = runWrappedHandler(current, args, wrappers[handlers]);

if (result && typeof result.then === 'function') {
return result.then(res => {
acc.push(res);

return acc;
});
}

acc.push(result);

return Promise.resolve(acc);
} catch (e) {
return Promise.reject(e);
}
}, []
);

const routeHandlers = route[handlers];

// if running onEnter, run handlers from parent to child
// if onLeave, run them from child to parent
return composedHandlers(
handlers === 'onEnter' ? routeHandlers : routeHandlers.reverse()
);
}
/* eslint-enable consistent-return */

0 comments on commit 26f2733

Please sign in to comment.