@@ -38,8 +38,6 @@ class IterableResult
38
38
@_closeCbPromise = null
39
39
40
40
@next = @_next
41
- @each = @_each
42
- @eachAsync = @_eachAsync
43
41
44
42
_addResponse : (response ) ->
45
43
if response .t is @_type or response .t is protoResponseType .SUCCESS_SEQUENCE
@@ -101,6 +99,9 @@ class IterableResult
101
99
@_responses .length is 0 or @_responses [0 ].r .length <= @_responseIndex
102
100
103
101
_promptNext : ->
102
+ if @_closeCbPromise ?
103
+ cb = @ _getCallback ()
104
+ cb new error.ReqlDriverError " Cursor is closed."
104
105
# If there are no more waiting callbacks, just wait until the next event
105
106
while @_cbQueue [0 ]?
106
107
if @ bufferEmpty () is true
@@ -188,11 +189,19 @@ class IterableResult
188
189
# called. Just return a promise that resolves
189
190
# immediately.
190
191
@_closeCbPromise = Promise .resolve ().nodeify (cb)
192
+ # Also clear any buffered results, so future calls to
193
+ # `next` fail.
194
+ @_responses = []
195
+ @_responseIndex = 0
191
196
else
192
197
# We aren't ended, and we need to. Create a promise
193
198
# that's resolved when the END query is acknowledged.
194
199
@_closeCbPromise = new Promise ((resolve , reject ) =>
195
200
@ _closeCb = (err ) =>
201
+ # Clear any buffered results, so future calls to
202
+ # `next` fail.
203
+ @_responses = []
204
+ @_responseIndex = 0
196
205
# Clear all callbacks for outstanding requests
197
206
while @_cbQueue .length > 0
198
207
@_cbQueue .shift ()
@@ -211,8 +220,7 @@ class IterableResult
211
220
).nodeify (cb)
212
221
return @_closeCbPromise
213
222
214
-
215
- _each : varar (1 , 2 , (cb , onFinished ) ->
223
+ each : varar (1 , 2 , (cb , onFinished ) ->
216
224
unless typeof cb is ' function'
217
225
throw new error.ReqlDriverError " First argument to each must be a function."
218
226
if onFinished? and typeof onFinished isnt ' function'
@@ -232,35 +240,88 @@ class IterableResult
232
240
@ _next nextCb
233
241
)
234
242
235
- _eachAsync : varar (1 , 2 , (cb , errCb ) ->
243
+ eachAsync : varar (1 , 3 , (cb , errCb , options = { concurrency : 1 } ) ->
236
244
unless typeof cb is ' function'
237
245
throw new error.ReqlDriverError ' First argument to eachAsync must be a function.'
238
246
239
- if errCb? and typeof errCb isnt ' function'
240
- throw new error.ReqlDriverError " Optional second argument to eachAsync must be a function"
247
+ if errCb?
248
+ if typeof errCb is ' object'
249
+ options = errCb
250
+ errCb = undefined
251
+ else if typeof errCb isnt ' function'
252
+ throw new error.ReqlDriverError " Optional second argument to eachAsync must be a function or `options` object"
253
+
254
+ unless options and typeof options .concurrency is ' number' and options .concurrency > 0
255
+ throw new error.ReqlDriverError " Optional `options.concurrency` argument to eachAsync must be a positive number"
241
256
257
+ pending = []
258
+
259
+ userCb = (data ) ->
260
+ if cb .length <= 1
261
+ ret = Promise .resolve (cb (data)) # either synchronous or awaits promise
262
+ else
263
+ handlerCalled = false
264
+ doneChecking = false
265
+ handlerArg = undefined
266
+ ret = Promise .fromNode (handler) ->
267
+ asyncRet = cb (data, (err ) ->
268
+ handlerCalled = true
269
+ if doneChecking
270
+ handler (err)
271
+ else
272
+ handlerArg = err
273
+ ) # callback-style async
274
+ unless asyncRet is undefined
275
+ handler (new error.ReqlDriverError " A two-argument row handler for eachAsync may only return undefined." )
276
+ else if handlerCalled
277
+ handler (handlerArg)
278
+ doneChecking = true
279
+ return ret
280
+ .then (data) ->
281
+ return data if data is undefined or typeof data is Promise
282
+ throw new error.ReqlDriverError " Row handler for eachAsync may only return a Promise or undefined."
242
283
nextCb = =>
243
284
if @_closeCbPromise ?
244
- return Promise .reject (new error.ReqlDriverError (" Cursor is closed." ))
285
+ return Promise .resolve ().then (data) ->
286
+ throw new error.ReqlDriverError " Cursor is closed."
245
287
else
246
- @ _next ().then (data) ->
247
- return cb (data) if cb .length <= 1 # either synchronous or awaits promise
248
- return Promise .fromNode (handler) -> cb (data, handler) # callback-style async
249
- .then (result) ->
250
- return nextCb ()
288
+ return @ _next ().then (data) ->
289
+ return data if pending .length < options .concurrency
290
+ return Promise .any (pending)
291
+ .catch Promise .AggregateError , (errs ) -> throw errs[0 ]
292
+ .return (data)
293
+ .then (data) ->
294
+ p = userCb (data).then ->
295
+ pending .splice pending .indexOf (p), 1
296
+ pending .push p
297
+ .then nextCb
251
298
.catch (err) ->
252
- return if err ? .message is ' No more rows in the cursor.'
253
- throw err
254
-
255
- return nextCb ().nodeify (errCb)
299
+ throw err if err ? .message isnt ' No more rows in the cursor.'
300
+ return Promise .all (pending) # await any queued promises before returning
301
+
302
+ resPromise = nextCb ().then () ->
303
+ errCb (null ) if errCb?
304
+ .catch (err) ->
305
+ return errCb (err) if errCb?
306
+ throw err
307
+ return resPromise unless errCb?
308
+ return null
256
309
)
257
310
311
+ _each : @ :: each
312
+ _eachAsync : @ :: eachAsync
313
+
258
314
toArray : varar 0 , 1 , (cb ) ->
259
315
if cb? and typeof cb isnt ' function'
260
316
throw new error.ReqlDriverCompileError " First argument to `toArray` must be a function or undefined."
261
317
262
318
results = []
263
- return @ eachAsync (results .push .bind (results)).return (results).nodeify (cb)
319
+ wrapper = (res ) =>
320
+ results .push (res)
321
+ return undefined
322
+ return @ eachAsync (wrapper).then (() =>
323
+ return results
324
+ ).nodeify (cb)
264
325
265
326
_makeEmitter : ->
266
327
@emitter = new EventEmitter
@@ -391,6 +452,8 @@ class ArrayResult extends IterableResult
391
452
392
453
_next : varar 0 , 1 , (cb ) ->
393
454
fn = (cb ) =>
455
+ if @_closeCbPromise ?
456
+ cb new error.ReqlDriverError " Cursor is closed."
394
457
if @ _hasNext () is true
395
458
self = @
396
459
if self .__index % @stackSize is @stackSize - 1
@@ -402,45 +465,35 @@ class ArrayResult extends IterableResult
402
465
else
403
466
cb new error.ReqlDriverError " No more rows in the cursor."
404
467
405
- new Promise ( (resolve , reject ) ->
406
- nextCb = (err , result ) ->
407
- if (err)
408
- reject (err)
409
- else
410
- resolve (result)
411
- fn (nextCb)
412
- ).nodeify cb
413
-
468
+ return Promise .fromNode (fn).nodeify (cb)
414
469
415
470
toArray : varar 0 , 1 , (cb ) ->
416
471
fn = (cb ) =>
472
+ if @_closeCbPromise ?
473
+ cb (new error.ReqlDriverError (" Cursor is closed." ))
474
+
417
475
# IterableResult.toArray would create a copy
418
476
if @__index ?
419
477
cb (null , @ .slice (@__index , @ .length ))
420
478
else
421
479
cb (null , @ )
422
480
423
- new Promise ( (resolve , reject ) ->
424
- toArrayCb = (err , result ) ->
425
- if (err)
426
- reject (err)
427
- else
428
- resolve (result)
429
- fn (toArrayCb)
430
- ).nodeify cb
431
-
481
+ return Promise .fromNode (fn).nodeify (cb)
432
482
433
- close : ->
434
- return @
483
+ close : varar 0 , 1 , (cb ) ->
484
+ # Clear the array
485
+ @ .length = 0
486
+ @__index = 0
487
+ # We set @_closeCbPromise so that functions such as `eachAsync`
488
+ # know that we have been closed and can error accordingly.
489
+ @_closeCbPromise = Promise .resolve ().nodeify (cb)
490
+ return @_closeCbPromise
435
491
436
492
makeIterable : (response ) ->
437
493
response .__proto__ = {}
438
494
for name, method of ArrayResult .prototype
439
495
if name isnt ' constructor'
440
- if name is ' _each'
441
- response .__proto__ [' each' ] = method
442
- response .__proto__ [' _each' ] = method
443
- else if name is ' _next'
496
+ if name is ' _next'
444
497
response .__proto__ [' next' ] = method
445
498
response .__proto__ [' _next' ] = method
446
499
else
0 commit comments