@@ -747,24 +747,60 @@ RestQuery.prototype.handleExcludeKeys = function() {
747
747
} ;
748
748
749
749
// 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.
750
764
RestQuery . prototype . handleInclude = function ( ) {
751
765
if ( this . include . length == 0 ) {
752
766
return ;
753
767
}
754
768
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.
757
784
const promises = { '' : Promise . resolve ( ) } ;
785
+
758
786
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 ( ( ) =>
763
795
includePath ( this . config , this . auth , this . response , path , this . restOptions )
764
796
) ;
797
+
798
+ // Put our promise into the promises map, so child paths can find and chain
799
+ // off of it
800
+ promises [ dottedPath ] = loadPromise ;
765
801
} ) ;
766
802
767
- // Wait for all includes to complete
803
+ // Wait for all includes to be fetched and merged in to the response tree
768
804
return Promise . all ( Object . values ( promises ) ) ;
769
805
} ;
770
806
@@ -964,7 +1000,9 @@ function replacePointers(object, path, replace) {
964
1000
if ( value instanceof Array ) {
965
1001
// the value can be a mixed array; be careful to only replace pointers!
966
1002
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
+ )
968
1006
. filter ( pointer => typeof pointer !== 'undefined' ) ;
969
1007
} else if ( value && value . __type === 'Pointer' ) {
970
1008
node [ attrName ] = replace [ value . objectId ] ;
0 commit comments