/
loaderRegistry.js
142 lines (128 loc) · 7.2 KB
/
loaderRegistry.js
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
(function(undefined) {
var loadingSubscribablesCache = {}, // Tracks component loads that are currently in flight
loadedDefinitionsCache = {}; // Tracks component loads that have already completed
ko.components = {
get: function(componentName, callback) {
var cachedDefinition = getObjectOwnProperty(loadedDefinitionsCache, componentName);
if (cachedDefinition) {
// It's already loaded and cached. Reuse the same definition object.
// Note that for API consistency, even cache hits complete asynchronously by default.
// You can bypass this by putting synchronous:true on your component config.
if (cachedDefinition.isSynchronousComponent) {
ko.dependencyDetection.ignore(function() { // See comment in loaderRegistryBehaviors.js for reasoning
callback(cachedDefinition.definition);
});
} else {
ko.tasks.schedule(function() { callback(cachedDefinition.definition); });
}
} else {
// Join the loading process that is already underway, or start a new one.
loadComponentAndNotify(componentName, callback);
}
},
clearCachedDefinition: function(componentName) {
delete loadedDefinitionsCache[componentName];
},
_getFirstResultFromLoaders: getFirstResultFromLoaders
};
function getObjectOwnProperty(obj, propName) {
return obj.hasOwnProperty(propName) ? obj[propName] : undefined;
}
function loadComponentAndNotify(componentName, callback) {
var subscribable = getObjectOwnProperty(loadingSubscribablesCache, componentName),
completedAsync;
if (!subscribable) {
// It's not started loading yet. Start loading, and when it's done, move it to loadedDefinitionsCache.
subscribable = loadingSubscribablesCache[componentName] = new ko.subscribable();
subscribable.subscribe(callback);
beginLoadingComponent(componentName, function(definition, config) {
var isSynchronousComponent = !!(config && config['synchronous']);
loadedDefinitionsCache[componentName] = { definition: definition, isSynchronousComponent: isSynchronousComponent };
delete loadingSubscribablesCache[componentName];
// For API consistency, all loads complete asynchronously. However we want to avoid
// adding an extra task schedule if it's unnecessary (i.e., the completion is already
// async).
//
// You can bypass the 'always asynchronous' feature by putting the synchronous:true
// flag on your component configuration when you register it.
if (completedAsync || isSynchronousComponent) {
// Note that notifySubscribers ignores any dependencies read within the callback.
// See comment in loaderRegistryBehaviors.js for reasoning
subscribable['notifySubscribers'](definition);
} else {
ko.tasks.schedule(function() {
subscribable['notifySubscribers'](definition);
});
}
});
completedAsync = true;
} else {
subscribable.subscribe(callback);
}
}
function beginLoadingComponent(componentName, callback) {
getFirstResultFromLoaders('getConfig', [componentName], function(config) {
if (config) {
// We have a config, so now load its definition
getFirstResultFromLoaders('loadComponent', [componentName, config], function(definition) {
callback(definition, config);
});
} else {
// The component has no config - it's unknown to all the loaders.
// Note that this is not an error (e.g., a module loading error) - that would abort the
// process and this callback would not run. For this callback to run, all loaders must
// have confirmed they don't know about this component.
callback(null, null);
}
});
}
function getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders) {
// On the first call in the stack, start with the full set of loaders
if (!candidateLoaders) {
candidateLoaders = ko.components['loaders'].slice(0); // Use a copy, because we'll be mutating this array
}
// Try the next candidate
var currentCandidateLoader = candidateLoaders.shift();
if (currentCandidateLoader) {
var methodInstance = currentCandidateLoader[methodName];
if (methodInstance) {
var wasAborted = false,
synchronousReturnValue = methodInstance.apply(currentCandidateLoader, argsExceptCallback.concat(function(result) {
if (wasAborted) {
callback(null);
} else if (result !== null) {
// This candidate returned a value. Use it.
callback(result);
} else {
// Try the next candidate
getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders);
}
}));
// Currently, loaders may not return anything synchronously. This leaves open the possibility
// that we'll extend the API to support synchronous return values in the future. It won't be
// a breaking change, because currently no loader is allowed to return anything except undefined.
if (synchronousReturnValue !== undefined) {
wasAborted = true;
// Method to suppress exceptions will remain undocumented. This is only to keep
// KO's specs running tidily, since we can observe the loading got aborted without
// having exceptions cluttering up the console too.
if (!currentCandidateLoader['suppressLoaderExceptions']) {
throw new Error('Component loaders must supply values by invoking the callback, not by returning values synchronously.');
}
}
} else {
// This candidate doesn't have the relevant handler. Synchronously move on to the next one.
getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders);
}
} else {
// No candidates returned a value
callback(null);
}
}
// Reference the loaders via string name so it's possible for developers
// to replace the whole array by assigning to ko.components.loaders
ko.components['loaders'] = [];
ko.exportSymbol('components', ko.components);
ko.exportSymbol('components.get', ko.components.get);
ko.exportSymbol('components.clearCachedDefinition', ko.components.clearCachedDefinition);
})();