Skip to content

Commit b2ed17a

Browse files
committed
refactor data hook resolution
1 parent 5ac8c1b commit b2ed17a

File tree

3 files changed

+109
-71
lines changed

3 files changed

+109
-71
lines changed

src/pipeline.js

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,10 @@ export function activate (view, transition, depth, cb, reuse) {
224224
}
225225

226226
if (activateHook) {
227-
transition.callHooks(activateHook, component, afterActivate, { cleanup })
227+
transition.callHooks(activateHook, component, afterActivate, {
228+
cleanup,
229+
postActivate: true
230+
})
228231
} else {
229232
afterActivate()
230233
}
@@ -257,46 +260,45 @@ export function reuse (view, transition) {
257260

258261
function loadData (component, transition, hook, cb, cleanup) {
259262
component.$loadingRouteData = true
260-
transition.callHooks(hook, component, (data, onError) => {
261-
// merge data from multiple data hooks
262-
if (Array.isArray(data) && data._needMerge) {
263-
data = data.reduce((res, obj) => {
264-
if (isPlainObject(obj)) {
265-
Object.keys(obj).forEach(key => {
266-
res[key] = obj[key]
267-
})
268-
}
269-
return res
270-
}, Object.create(null))
271-
}
272-
// handle promise sugar syntax
273-
const promises = []
274-
if (isPlainObject(data)) {
275-
Object.keys(data).forEach(key => {
276-
const val = data[key]
277-
if (isPromise(val)) {
278-
promises.push(val.then(resolvedVal => {
279-
component.$set(key, resolvedVal)
280-
}))
281-
} else {
282-
component.$set(key, val)
283-
}
284-
})
285-
}
286-
if (!promises.length) {
287-
component.$loadingRouteData = false
288-
component.$emit('route-data-loaded', component)
289-
cb && cb()
290-
} else {
291-
promises[0].constructor.all(promises).then(() => {
263+
transition.callHooks(hook, component, cb, {
264+
cleanup,
265+
postActivate: true,
266+
processData: (data) => {
267+
// merge data from multiple data hooks
268+
if (Array.isArray(data) && data._needMerge) {
269+
data = data.reduce((res, obj) => {
270+
if (isPlainObject(obj)) {
271+
Object.keys(obj).forEach(key => {
272+
res[key] = obj[key]
273+
})
274+
}
275+
return res
276+
}, Object.create(null))
277+
}
278+
// handle promise sugar syntax
279+
const promises = []
280+
if (isPlainObject(data)) {
281+
Object.keys(data).forEach(key => {
282+
const val = data[key]
283+
if (isPromise(val)) {
284+
promises.push(val.then(resolvedVal => {
285+
component.$set(key, resolvedVal)
286+
}))
287+
} else {
288+
component.$set(key, val)
289+
}
290+
})
291+
}
292+
const onDataLoaded = () => {
292293
component.$loadingRouteData = false
293294
component.$emit('route-data-loaded', component)
294-
cb && cb()
295-
}, onError)
295+
}
296+
if (!promises.length) {
297+
onDataLoaded()
298+
} else {
299+
return promises[0].constructor.all(promises).then(onDataLoaded)
300+
}
296301
}
297-
}, {
298-
cleanup: cleanup,
299-
expectData: true
300302
})
301303
}
302304

src/transition.js

Lines changed: 64 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -190,13 +190,15 @@ export default class RouteTransition {
190190
* @param {Function} [cb]
191191
* @param {Object} [options]
192192
* - {Boolean} expectBoolean
193-
* - {Boolean} expectData
193+
* - {Boolean} postActive
194+
* - {Function} processData
194195
* - {Function} cleanup
195196
*/
196197

197198
callHook (hook, context, cb, {
198199
expectBoolean = false,
199-
expectData = false,
200+
postActivate = false,
201+
processData,
200202
cleanup
201203
} = {}) {
202204

@@ -210,19 +212,30 @@ export default class RouteTransition {
210212
}
211213

212214
// handle errors
213-
const onError = (err) => {
214-
// cleanup indicates an after-activation hook,
215-
// so instead of aborting we just let the transition
216-
// finish.
217-
cleanup ? next() : abort()
215+
const onError = err => {
216+
if (postActivate) {
217+
if (!nextCalled) next()
218+
} else {
219+
abort()
220+
}
218221
if (err && !transition.router._suppress) {
219222
warn('Uncaught error during transition: ')
220223
throw err instanceof Error ? err : new Error(err)
221224
}
222225
}
223226

227+
// since promise swallows errors, we have to
228+
// throw it in the next tick...
229+
const onPromiseError = err => {
230+
try {
231+
onError(err)
232+
} catch (e) {
233+
setTimeout(() => { throw e }, 0)
234+
}
235+
}
236+
224237
// advance the transition to the next step
225-
const next = (data) => {
238+
const next = () => {
226239
if (nextCalled) {
227240
warn('transition.next() should be called only once.')
228241
return
@@ -232,7 +245,33 @@ export default class RouteTransition {
232245
cleanup && cleanup()
233246
return
234247
}
235-
cb && cb(data, onError)
248+
cb && cb()
249+
}
250+
251+
const nextWithBoolean = res => {
252+
if (typeof res === 'boolean') {
253+
res ? next() : abort()
254+
} else if (isPromise(res)) {
255+
res.then((ok) => {
256+
ok ? next() : abort()
257+
}, onPromiseError)
258+
} else if (!hook.length) {
259+
next()
260+
}
261+
}
262+
263+
const nextWithData = data => {
264+
let res
265+
try {
266+
res = processData(data)
267+
} catch (err) {
268+
return onError(err)
269+
}
270+
if (isPromise(res)) {
271+
res.then(next, onPromiseError)
272+
} else {
273+
next()
274+
}
236275
}
237276

238277
// expose a clone of the transition object, so that each
@@ -242,7 +281,7 @@ export default class RouteTransition {
242281
to: transition.to,
243282
from: transition.from,
244283
abort: abort,
245-
next: next,
284+
next: processData ? nextWithData : next,
246285
redirect: function () {
247286
transition.redirect.apply(transition, arguments)
248287
}
@@ -256,22 +295,21 @@ export default class RouteTransition {
256295
return onError(err)
257296
}
258297

259-
// handle boolean/promise return values
260-
const resIsPromise = isPromise(res)
261298
if (expectBoolean) {
262-
if (typeof res === 'boolean') {
263-
res ? next() : abort()
264-
} else if (resIsPromise) {
265-
res.then((ok) => {
266-
ok ? next() : abort()
267-
}, onError)
268-
} else if (!hook.length) {
269-
next(res)
299+
// boolean hooks
300+
nextWithBoolean(res)
301+
} else if (isPromise(res)) {
302+
// promise
303+
if (processData) {
304+
res.then(nextWithData, onPromiseError)
305+
} else {
306+
res.then(next, onPromiseError)
270307
}
271-
} else if (resIsPromise) {
272-
res.then(next, onError)
273-
} else if ((expectData && isPlainOjbect(res)) || !hook.length) {
274-
next(res)
308+
} else if (processData && isPlainOjbect(res)) {
309+
// data promise sugar
310+
nextWithData(res)
311+
} else if (!hook.length) {
312+
next()
275313
}
276314
}
277315

@@ -288,17 +326,15 @@ export default class RouteTransition {
288326
if (Array.isArray(hooks)) {
289327
const res = []
290328
res._needMerge = true
291-
let onError
292329
this.runQueue(hooks, (hook, _, next) => {
293330
if (!this.aborted) {
294-
this.callHook(hook, context, (r, onError) => {
331+
this.callHook(hook, context, r => {
295332
if (r) res.push(r)
296-
onError = onError
297333
next()
298334
}, options)
299335
}
300336
}, () => {
301-
cb(res, onError)
337+
cb(res)
302338
})
303339
} else {
304340
this.callHook(hooks, context, cb, options)

test/unit/specs/pipeline/data.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ describe('data', function () {
125125
})
126126
})
127127

128-
it('promise error', function (done) {
128+
it('promise reject', function (done) {
129129
test({
130130
data: {
131131
data: function () {
@@ -141,8 +141,8 @@ describe('data', function () {
141141
assertCalls(calls, ['data.data'])
142142
expect(router.app.$el.textContent).toBe('loading...')
143143
setTimeout(function () {
144-
// should complete the transition despite error
145-
expect(router.app.$el.textContent).toBe('')
144+
// should not abort, but nor should finish loading data
145+
expect(router.app.$el.textContent).toBe('loading...')
146146
done()
147147
}, wait * 2)
148148
})
@@ -190,8 +190,8 @@ describe('data', function () {
190190
assertCalls(calls, ['data.data'])
191191
expect(router.app.$el.textContent).toBe('loading...')
192192
setTimeout(function () {
193-
// should complete the transition despite error
194-
expect(router.app.$el.textContent).toBe('')
193+
// should not abort, but nor should finish loading data
194+
expect(router.app.$el.textContent).toBe('loading...')
195195
done()
196196
}, wait * 2)
197197
})

0 commit comments

Comments
 (0)