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

Next #47

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open

Next #47

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 16 additions & 13 deletions demo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,29 @@ highway.route({
path: '/',
before: [
{ name: 'test-event', params: [1, 2, 3, 4] },
(state) => {
async () => {
console.log('before middleware')
setTimeout(() => {
console.log('resolve before middleware')
state.resolve()
}, 2000)

return new Promise(resolve => {
setTimeout(() => {
console.log('resolve before middleware')
resolve()
}, 2000)
})
}
],
action (state) {
async action () {
console.log('home controller')

setTimeout(() => {
state.resolve('youhou!')
}, 2000)
return new Promise(resolve => {
setTimeout(() => {
resolve('youhou!')
}, 2000)
})
},
after: [
() => {
console.log('after middleware')
(state) => {
console.log(`after middleware`, state)
}
]
})
Expand All @@ -55,7 +60,6 @@ highway.route({
action (state) {
console.log(state)
console.log(`user controller for user #${state.params.id}`)
state.resolve()
}
})

Expand All @@ -77,6 +81,5 @@ highway.route({
path: '/action/query',
action (state) {
console.log('action query state', state)
state.resolve()
}
})
64 changes: 32 additions & 32 deletions src/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,43 +68,41 @@ Route.prototype = {
const { name, path, action, before, after } = this.definition

// Wrap the route action
return function actionWrapper (...args) {
return async function actionWrapper (...args) {
// Convert args to object
const params = urlComposer.params(path, args)

// Create promise for async handling of controller execution
return new Promise((resolve, reject) => {
// Trigger `before` events/middlewares
if (before) {
return trigger.exec({ name, events: before, params })
.then(
// Execute original route action passing route params and promise flow controls
() => Promise.resolve(
action({ resolve, reject, params, query: parseQuery() })
),
() => reject(
new Error(`[ backbone-highway ] Route "${name}" was rejected by a "before" middleware`)
)
)
const query = parseQuery()

// Trigger `before` events/middlewares
if (before) {
try {
await trigger.exec({ name, events: before, params, query })
} catch (err) {
throw new Error(`[backbone-highway] Route "${name}" was rejected by a "before" middleware`)
}
}

// Just execute action if no `before` events are declared
return Promise.resolve(
action({ resolve, reject, params, query: parseQuery() })
// Execute route action and get result
let result
try {
// Wrap action method in a `Promise.resolve()` in case action is not `async`
result = await Promise.resolve(
action({ params, query })
)
})
// Wait for promise resolve
.then(result => {
// Trigger `after` events/middlewares
if (after) {
return trigger.exec({ name, events: after, params })
}

return true
}).catch(err => {
// TODO What should we do when the action is rejected
console.error('caught action error', err)
})
} catch (err) {
throw new Error(`[backbone-highway] Route "${name}" was rejected by "action"`)
}

// Trigger `before` events/middlewares
if (after) {
try {
await trigger.exec({ name, events: after, params, query, result })
} catch (err) {
throw new Error(`[backbone-higway] Route "${name}" was rejected by an "after" middleware`)
}
}

return true
}
},

Expand All @@ -113,6 +111,8 @@ Route.prototype = {
}
}

// Parse query params from `window.location.search` and return an object
// TODO move to `url-composer` or maybe use `query-string`
function parseQuery () {
const result = {}
let query = window.location.search || ''
Expand Down
28 changes: 14 additions & 14 deletions src/trigger.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,44 @@ import _ from 'underscore'
import store from './store'

export default {
dispatch (evt, params) {
dispatch ({ evt, params, query, result }) {
const { dispatcher } = store.get('options')

if (_.isString(evt)) {
evt = { name: evt }
}
if (_.isString(evt)) evt = { name: evt }

if (!dispatcher) {
throw new Error(`[ highway ] Event '${evt.name}' could not be triggered, missing dispatcher`)
}

params = evt.params || params

console.log(`Trigger event ${evt.name}, params:`, params)

dispatcher.trigger(evt.name, { params })
dispatcher.trigger(evt.name, { params, query, result })
},

exec (options) {
let { name, events, params } = options
let { name, events, params, query, result } = options

if (!_.isEmpty && !_.isArray(events)) {
throw new Error(`[ highway ] Route events definition for ${name} needs to be an Array`)
}

// Normalize events as an array
if (!_.isArray(events)) events = [events]

return Promise.all(
_.map(events, (evt) => {
// Handle event as a function
if (_.isFunction(evt)) {
return new Promise((resolve, reject) => {
evt({ resolve, reject, params })
return null
})
// Wrap in a promise in case `evt` is not async
return Promise.resolve(
evt({ params, query, result })
)
}

this.dispatch(evt, params)
return Promise.resolve()
// Else dispatch event to
this.dispatch({ evt, params, query, result })

return true
})
)
}
Expand Down
109 changes: 95 additions & 14 deletions test/spec/highway.spec.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
const assert = require('assert')
// const defer = require('lodash/defer')
const isFunction = require('lodash/isFunction')
const isObject = require('lodash/isObject')
import assert from 'assert'
import { Events } from 'backbone'
import { isFunction, isObject, isString, extend } from 'lodash'

const highway = require('../../dist/backbone-highway')
import highway from '../../src/index'

const location = window.location
const AppEvents = extend({}, Events)

const definitions = {
home: {
name: 'home',
path: '/',
action (state) {
return state.resolve()
async action (state) {
return true
}
},
profile: {
name: 'profile',
path: '/users/:id',
action (state) {
return state.resolve(state.params.id)
async action (state) {
return state.params.id
}
},
optional: {
name: 'optional',
path: '/optional(/path/:param)',
action (state) {
return state.resolve(state.params.param)
async action (state) {
return state.params.param
}
}
}
Expand Down Expand Up @@ -76,7 +76,9 @@ describe('Backbone.Highway', () => {
})

it('should start the router using `start` method', () => {
highway.start()
highway.start({
dispatcher: AppEvents
})
})

it('should execute routes using the `go` method', () => {
Expand Down Expand Up @@ -151,11 +153,10 @@ describe('Backbone.Highway', () => {
highway.route({
name: 'test-action-query',
path: '/test/action/query',
action (state) {
async action (state) {
assert.ok(isObject(state.query))
assert.equal(state.query.hello, 'world')

state.resolve()
done()
}
})
Expand All @@ -164,4 +165,84 @@ describe('Backbone.Highway', () => {
highway.go({ name: 'test-action-query', query: { hello: 'world' } })
)
})

it('should handle `before` events', (done) => {
highway.route({
name: 'before-events',
path: '/before/:data',
before: [
async ({ params }) => {
assert.equal(params.data, 'events')
}
],
async action ({ params }) {
assert.equal(params.data, 'events')
done()
}
})

assert.ok(
highway.go({ name: 'before-events', params: { data: 'events' } })
)
})

it('should dispatch named `before` events', (done) => {
AppEvents.on('before-test-event', ({ params }) => {
assert.ok(isObject(params))
assert.equal(params.what, 'events')
done()
})

highway.route({
name: 'named-before-events',
path: '/named/before/test/:what',
before: [
'before-test-event'
],
action () {}
})

assert.ok(
highway.go({ name: 'named-before-events', params: { what: 'events' } })
)

AppEvents.off('before-test-event')
})

it('should handle `after` events', (done) => {
highway.route({
name: 'after-events',
path: '/after/:data',
async action ({ params }) {
assert.equal(params.data, 'events')

return 'yeah'
},
after: [
async ({ params, result }) => {
assert.equal(params.data, 'events')
assert.equal(result, 'yeah')
done()
}
]
})

assert.ok(
highway.go({ name: 'after-events', params: { data: 'events' } })
)
})

it('should execute 404 controller for missing routes', (done) => {
highway.route({
name: '404',
action ({ params }) {
assert.ok(isObject(params))
done()
}
})

assert.ok(
!highway.go({ name: 'some-random-inexisting-route' })
)
})
})