Skip to content

Commit 03c02cd

Browse files
committed
Improve readability of RestQuery.handleInclude
There's quite a bit of trickiness going on here, so for the benefit of future maintainers, lets add some documentation and clear up some variable names. - Documented the preconditions that we expect when we're entering this function. These are all met by the current implementation of the caller, but it's helpful to someone trying to verify the correctness of this function. - Added a lengthier description of what is going on with the map of `promises` - Renamed some variables to help make their purpose clearer
1 parent bc03c06 commit 03c02cd

File tree

1 file changed

+46
-8
lines changed

1 file changed

+46
-8
lines changed

src/RestQuery.js

+46-8
Original file line numberDiff line numberDiff line change
@@ -747,24 +747,60 @@ RestQuery.prototype.handleExcludeKeys = function() {
747747
};
748748

749749
// Augments this.response with data at the paths provided in this.include.
750+
//
751+
// Preconditions:
752+
// - `this.include` is an array of arrays of strings; (in flow parlance, Array<Array<string>>)
753+
//
754+
// - `this.include` is de-duplicated. This ensures that we don't try to fetch
755+
// the same objects twice.
756+
//
757+
// - For each value in `this.include` with length > 1, there is also
758+
// an earlier value for the prefix of that value.
759+
//
760+
// Example: ['a', 'b', 'c'] in the array implies that ['a', 'b'] is also in
761+
// the array, at an earlier position).
762+
//
763+
// This prevents trying to follow pointers on unfetched objects.
750764
RestQuery.prototype.handleInclude = function() {
751765
if (this.include.length == 0) {
752766
return;
753767
}
754768

755-
// Construct a graph of promises, ensuring that we don't try to load
756-
// any path until it's prefix has finished loading.
769+
// The list of includes form a sort of a tree - Each path should wait to
770+
// start trying to load until its parent path has finished loading (so that
771+
// the pointers it is trying to read and fetch are in the object tree).
772+
//
773+
// So, for instance, if we have an include of ['a', 'b', 'c'], that must
774+
// wait on the include of ['a', 'b'] to finish, which must wait on the include
775+
// of ['a'] to finish.
776+
//
777+
// This `promises` object is a map of dotted paths to promises that resolve
778+
// when that path has finished loading into the tree. One special case is the
779+
// empty path (represented by the empty string). This represents the root of
780+
// the tree, which is `this.response` and is already fetched. We set a
781+
// pre-resolved promise at that level, meaning that include paths with only
782+
// one component (like `['a']`) will chain onto that resolved promise and
783+
// are immediately unblocked.
757784
const promises = { '': Promise.resolve() };
785+
758786
this.include.forEach(path => {
759-
const prefix = path.slice(0, -1).join('.');
760-
const key = path.join('.');
761-
const loadAfter = promises[prefix];
762-
promises[key] = loadAfter.then(() =>
787+
const dottedPath = path.join('.');
788+
789+
// Get the promise for the parent path
790+
const parentDottedPath = path.slice(0, -1).join('.');
791+
const parentPromise = promises[parentDottedPath];
792+
793+
// Once the parent promise has resolved, do this path's load step
794+
const loadPromise = parentPromise.then(() =>
763795
includePath(this.config, this.auth, this.response, path, this.restOptions)
764796
);
797+
798+
// Put our promise into the promises map, so child paths can find and chain
799+
// off of it
800+
promises[dottedPath] = loadPromise;
765801
});
766802

767-
// Wait for all includes to complete
803+
// Wait for all includes to be fetched and merged in to the response tree
768804
return Promise.all(Object.values(promises));
769805
};
770806

@@ -964,7 +1000,9 @@ function replacePointers(object, path, replace) {
9641000
if (value instanceof Array) {
9651001
// the value can be a mixed array; be careful to only replace pointers!
9661002
node[attrName] = value
967-
.map(obj => obj && obj.__type === 'Pointer' ? replace[obj.objectId] : obj)
1003+
.map(obj =>
1004+
obj && obj.__type === 'Pointer' ? replace[obj.objectId] : obj
1005+
)
9681006
.filter(pointer => typeof pointer !== 'undefined');
9691007
} else if (value && value.__type === 'Pointer') {
9701008
node[attrName] = replace[value.objectId];

0 commit comments

Comments
 (0)