@@ -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.
750764RestQuery . 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