@@ -54,14 +54,14 @@ class DataLoader<K, V, C = K> {
54
54
this . _batchLoadFn = batchLoadFn ;
55
55
this . _options = options ;
56
56
this . _promiseCache = getValidCacheMap ( options ) ;
57
- this . _queue = [ ] ;
57
+ this . _batch = null ;
58
58
}
59
59
60
60
// Private
61
61
_batchLoadFn : BatchLoadFn < K , V > ;
62
62
_options : ?Options < K , V , C > ;
63
63
_promiseCache : ?CacheMap < C , Promise < V >> ;
64
- _queue : LoaderQueue < K , V > ;
64
+ _batch : Batch < K , V > | null ;
65
65
66
66
/**
67
67
* Loads a key, returning a `Promise` for the value represented by that key.
@@ -76,7 +76,7 @@ class DataLoader<K, V, C = K> {
76
76
77
77
// Determine options
78
78
var options = this . _options ;
79
- var shouldBatch = ! options || options . batch !== false ;
79
+ var batch = getCurrentBatch ( this ) ;
80
80
var cache = this . _promiseCache ;
81
81
var cacheKey = getCacheKey ( options , key ) ;
82
82
@@ -88,23 +88,11 @@ class DataLoader<K, V, C = K> {
88
88
}
89
89
}
90
90
91
- // Otherwise, produce a new Promise for this value.
91
+ // Otherwise, produce a new Promise for this key, and enqueue it to be
92
+ // dispatched along with the current batch.
93
+ batch . keys . push ( key ) ;
92
94
var promise = new Promise ( ( resolve , reject ) => {
93
- // Enqueue this Promise to be dispatched.
94
- this . _queue . push ( { key, resolve, reject } ) ;
95
-
96
- // Determine if a dispatch of this queue should be scheduled.
97
- // A single dispatch should be scheduled per queue at the time when the
98
- // queue changes from "empty" to "full".
99
- if ( this . _queue . length === 1 ) {
100
- if ( shouldBatch ) {
101
- // If batching, schedule a task to dispatch the queue.
102
- enqueuePostPromiseJob ( ( ) => dispatchQueue ( this ) ) ;
103
- } else {
104
- // Otherwise dispatch the (queue of one) immediately.
105
- dispatchQueue ( this ) ;
106
- }
107
- }
95
+ batch . callbacks . push ( { resolve, reject } ) ;
108
96
} ) ;
109
97
110
98
// If caching, cache this promise.
@@ -246,43 +234,61 @@ var enqueuePostPromiseJob =
246
234
// Private: cached resolved Promise instance
247
235
var resolvedPromise ;
248
236
249
- // Private: given the current state of a Loader instance, perform a batch load
250
- // from its current queue.
251
- function dispatchQueue < K , V > ( loader : DataLoader < K , V , any > ) {
252
- // Take the current loader queue, replacing it with an empty queue.
253
- var queue = loader . _queue ;
254
- loader . _queue = [ ] ;
255
-
256
- // If a maxBatchSize was provided and the queue is longer, then segment the
257
- // queue into multiple batches, otherwise treat the queue as a single batch.
258
- var maxBatchSize = loader . _options && loader . _options . maxBatchSize ;
259
- if ( maxBatchSize && maxBatchSize > 0 && maxBatchSize < queue . length ) {
260
- for ( var i = 0 ; i < queue . length / maxBatchSize ; i ++ ) {
261
- dispatchQueueBatch (
262
- loader ,
263
- queue . slice ( i * maxBatchSize , ( i + 1 ) * maxBatchSize )
264
- ) ;
265
- }
266
- } else {
267
- dispatchQueueBatch ( loader , queue ) ;
237
+ // Private: Describes a batch of requests
238
+ type Batch < K , V > = {
239
+ hasDispatched : boolean ,
240
+ keys : Array < K > ,
241
+ callbacks : Array < {
242
+ resolve : ( value : V ) => void ;
243
+ reject : ( error : Error ) = > void ;
244
+ } >
245
+ }
246
+
247
+ // Private: Either returns the current batch, or creates and schedules a
248
+ // dispatch of a new batch for the given loader.
249
+ function getCurrentBatch < K , V > ( loader : DataLoader < K , V , any > ) : Batch < K , V > {
250
+ var options = loader . _options ;
251
+ var maxBatchSize =
252
+ ( options && options . maxBatchSize ) ||
253
+ ( options && options . batch === false ? 1 : 0 ) ;
254
+
255
+ // If there is an existing batch which has not yet dispatched and is within
256
+ // the limit of the batch size, then return it.
257
+ var existingBatch = loader . _batch ;
258
+ if (
259
+ existingBatch !== null &&
260
+ ! existingBatch . hasDispatched &&
261
+ ( maxBatchSize === 0 || existingBatch . keys . length < maxBatchSize )
262
+ ) {
263
+ return existingBatch ;
268
264
}
265
+
266
+ // Otherwise, create a new batch for this loader.
267
+ var newBatch = { hasDispatched : false , keys : [ ] , callbacks : [ ] } ;
268
+
269
+ // Store it on the loader so it may be reused.
270
+ loader . _batch = newBatch ;
271
+
272
+ // Then schedule a task to dispatch this batch of requests.
273
+ enqueuePostPromiseJob ( ( ) => dispatchBatch ( loader , newBatch ) ) ;
274
+
275
+ return newBatch ;
269
276
}
270
277
271
- function dispatchQueueBatch < K , V > (
278
+ function dispatchBatch < K , V > (
272
279
loader : DataLoader < K , V , any > ,
273
- queue : LoaderQueue < K , V >
280
+ batch : Batch < K , V >
274
281
) {
275
- // Collect all keys to be loaded in this dispatch
276
- var keys = queue . map ( ( { key } ) => key ) ;
282
+ // Mark this batch as having been dispatched.
283
+ batch . hasDispatched = true ;
277
284
278
- // Call the provided batchLoadFn for this loader with the loader queue's keys.
279
- var batchLoadFn = loader . _batchLoadFn ;
280
- // Call with the loader as the `this` context.
281
- var batchPromise = batchLoadFn . call ( loader , keys ) ;
285
+ // Call the provided batchLoadFn for this loader with the batch's keys and
286
+ // with the loader as the `this` context.
287
+ var batchPromise = loader . _batchLoadFn ( batch . keys ) ;
282
288
283
289
// Assert the expected response from batchLoadFn
284
290
if ( ! batchPromise || typeof batchPromise . then !== 'function' ) {
285
- return failedDispatch ( loader , queue , new TypeError (
291
+ return failedDispatch ( loader , batch , new TypeError (
286
292
'DataLoader must be constructed with a function which accepts ' +
287
293
'Array<key> and returns Promise<Array<value>>, but the function did ' +
288
294
`not return a Promise: ${ String ( batchPromise ) } .`
@@ -300,41 +306,40 @@ function dispatchQueueBatch<K, V>(
300
306
`not return a Promise of an Array: ${ String ( values ) } .`
301
307
) ;
302
308
}
303
- if ( values . length !== keys . length ) {
309
+ if ( values . length !== batch . keys . length ) {
304
310
throw new TypeError (
305
311
'DataLoader must be constructed with a function which accepts ' +
306
312
'Array<key> and returns Promise<Array<value>>, but the function did ' +
307
313
'not return a Promise of an Array of the same length as the Array ' +
308
314
'of keys.' +
309
- `\n\nKeys:\n${ String ( keys ) } ` +
315
+ `\n\nKeys:\n${ String ( batch . keys ) } ` +
310
316
`\n\nValues:\n${ String ( values ) } `
311
317
) ;
312
318
}
313
319
314
- // Step through the values, resolving or rejecting each Promise in the
315
- // loaded queue.
316
- queue . forEach ( ( { resolve, reject } , index ) => {
317
- var value = values [ index ] ;
320
+ // Step through values, resolving or rejecting each Promise in the batch.
321
+ for ( var i = 0 ; i < batch . callbacks . length ; i ++ ) {
322
+ var value = values [ i ] ;
318
323
if ( value instanceof Error ) {
319
- reject ( value ) ;
324
+ batch . callbacks [ i ] . reject ( value ) ;
320
325
} else {
321
- resolve ( value ) ;
326
+ batch . callbacks [ i ] . resolve ( value ) ;
322
327
}
323
- } ) ;
324
- } ) . catch ( error => failedDispatch ( loader , queue , error ) ) ;
328
+ }
329
+ } ) . catch ( error => failedDispatch ( loader , batch , error ) ) ;
325
330
}
326
331
327
332
// Private: do not cache individual loads if the entire batch dispatch fails,
328
333
// but still reject each request so they do not hang.
329
334
function failedDispatch < K , V > (
330
335
loader : DataLoader < K , V , any > ,
331
- queue : LoaderQueue < K , V > ,
336
+ batch : Batch < K , V > ,
332
337
error : Error
333
338
) {
334
- queue . forEach ( ( { key , reject } ) => {
335
- loader . clear ( key ) ;
336
- reject ( error ) ;
337
- } ) ;
339
+ for ( var i = 0 ; i < batch . keys . length ; i ++ ) {
340
+ loader . clear ( batch . keys [ i ] ) ;
341
+ batch . callbacks [ i ] . reject ( error ) ;
342
+ }
338
343
}
339
344
340
345
// Private: produce a cache key for a given key (and options)
@@ -369,13 +374,6 @@ function getValidCacheMap<K, V, C>(
369
374
return cacheMap ;
370
375
}
371
376
372
- // Private
373
- type LoaderQueue < K , V> = Array < {
374
- key : K ;
375
- resolve : ( value : V ) = > void ;
376
- reject : ( error : Error ) = > void ;
377
- } > ;
378
-
379
377
// Private
380
378
function isArrayLike ( x : mixed ) : boolean {
381
379
return (
0 commit comments