forked from angular-ui/ui-router
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathresolvable.ts
104 lines (90 loc) · 3.93 KB
/
resolvable.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
/** @module path */ /** for typedoc */
import {extend, pick, map, filter} from "../common/common";
import {not} from "../common/hof";
import {isInjectable} from "../common/predicates";
import {services} from "../common/coreservices";
import {trace} from "../common/trace";
import {Resolvables, IOptions1} from "./interface";
import {ResolveContext} from "./resolveContext";
/**
* The basic building block for the resolve system.
*
* Resolvables encapsulate a state's resolve's resolveFn, the resolveFn's declared dependencies, the wrapped (.promise),
* and the unwrapped-when-complete (.data) result of the resolveFn.
*
* Resolvable.get() either retrieves the Resolvable's existing promise, or else invokes resolve() (which invokes the
* resolveFn) and returns the resulting promise.
*
* Resolvable.get() and Resolvable.resolve() both execute within a context path, which is passed as the first
* parameter to those fns.
*/
export class Resolvable {
constructor(name: string, resolveFn: Function, preResolvedData?: any) {
extend(this, {
name,
resolveFn,
deps: services.$injector.annotate(resolveFn, services.$injector.strictDi),
data: preResolvedData
});
}
name: string;
resolveFn: Function;
deps: string[];
promise: Promise<any> = undefined;
data: any;
// synchronous part:
// - sets up the Resolvable's promise
// - retrieves dependencies' promises
// - returns promise for async part
// asynchronous part:
// - wait for dependencies promises to resolve
// - invoke the resolveFn
// - wait for resolveFn promise to resolve
// - store unwrapped data
// - resolve the Resolvable's promise
resolveResolvable(resolveContext: ResolveContext, options: IOptions1 = {}) {
let {name, deps, resolveFn} = this;
trace.traceResolveResolvable(this, options);
// First, set up an overall deferred/promise for this Resolvable
let deferred = services.$q.defer();
this.promise = deferred.promise;
// Load a map of all resolvables for this state from the context path
// Omit the current Resolvable from the result, so we don't try to inject this into this
let ancestorsByName: Resolvables = resolveContext.getResolvables(null, { omitOwnLocals: [ name ] });
// Limit the ancestors Resolvables map to only those that the current Resolvable fn's annotations depends on
let depResolvables: Resolvables = <any> pick(ancestorsByName, deps);
// Get promises (or synchronously invoke resolveFn) for deps
let depPromises: any = map(depResolvables, (resolvable: Resolvable) => resolvable.get(resolveContext, options));
// Return a promise chain that waits for all the deps to resolve, then invokes the resolveFn passing in the
// dependencies as locals, then unwraps the resulting promise's data.
return services.$q.all(depPromises).then(locals => {
try {
let result = services.$injector.invoke(resolveFn, null, locals);
deferred.resolve(result);
} catch (error) {
deferred.reject(error);
}
return this.promise;
}).then(data => {
this.data = data;
trace.traceResolvableResolved(this, options);
return this.promise;
});
}
get(resolveContext: ResolveContext, options?: IOptions1): Promise<any> {
return this.promise || this.resolveResolvable(resolveContext, options);
}
toString() {
return `Resolvable(name: ${this.name}, requires: [${this.deps}])`;
}
/**
* Validates the result map as a "resolve:" style object, then transforms the resolves into Resolvables
*/
static makeResolvables(resolves: { [key: string]: Function; }): Resolvables {
// If a hook result is an object, it should be a map of strings to functions.
let invalid = filter(resolves, not(isInjectable)), keys = Object.keys(invalid);
if (keys.length)
throw new Error(`Invalid resolve key/value: ${keys[0]}/${invalid[keys[0]]}`);
return map(resolves, (fn, name: string) => new Resolvable(name, fn));
}
}