2
2
3
3
const {
4
4
SymbolAsyncIterator,
5
- SymbolIterator
5
+ SymbolIterator,
6
+ Promise
6
7
} = primordials ;
7
8
const { Buffer } = require ( 'buffer' ) ;
8
9
@@ -11,7 +12,6 @@ const {
11
12
} = require ( 'internal/errors' ) . codes ;
12
13
13
14
function from ( Readable , iterable , opts ) {
14
- let iterator ;
15
15
if ( typeof iterable === 'string' || iterable instanceof Buffer ) {
16
16
return new Readable ( {
17
17
objectMode : true ,
@@ -23,41 +23,73 @@ function from(Readable, iterable, opts) {
23
23
} ) ;
24
24
}
25
25
26
- if ( iterable && iterable [ SymbolAsyncIterator ] )
27
- iterator = iterable [ SymbolAsyncIterator ] ( ) ;
28
- else if ( iterable && iterable [ SymbolIterator ] )
29
- iterator = iterable [ SymbolIterator ] ( ) ;
30
- else
31
- throw new ERR_INVALID_ARG_TYPE ( 'iterable' , [ 'Iterable' ] , iterable ) ;
26
+ let onDataNeeded ;
32
27
33
28
const readable = new Readable ( {
34
29
objectMode : true ,
35
- ...opts
30
+ ...opts ,
31
+ read ( ) {
32
+ onDataNeeded && onDataNeeded ( ) ;
33
+ } ,
34
+ async destroy ( error , cb ) {
35
+ onDataNeeded && onDataNeeded ( ) ;
36
+ try {
37
+ await pumping ;
38
+ } catch ( e ) {
39
+ // Do not hide present error
40
+ if ( ! error ) error = e ;
41
+ }
42
+ cb ( error ) ;
43
+ } ,
36
44
} ) ;
37
- // Reading boolean to protect against _read
38
- // being called before last iteration completion.
39
- let reading = false ;
40
- readable . _read = function ( ) {
41
- if ( ! reading ) {
42
- reading = true ;
43
- next ( ) ;
44
- }
45
- } ;
46
- async function next ( ) {
45
+
46
+ if ( ! iterable [ SymbolAsyncIterator ] && ! iterable [ SymbolIterator ] )
47
+ throw new ERR_INVALID_ARG_TYPE ( 'iterable' , [ 'Iterable' ] , iterable ) ;
48
+
49
+ const pumping = pump ( ) ;
50
+
51
+ return readable ;
52
+
53
+ async function pump ( ) {
54
+ /*
55
+ We're iterating over sync or async iterator with the appropriate sync
56
+ or async version of the `for-of` loop.
57
+
58
+ `for-await-of` loop has an edge case when looping over synchronous
59
+ iterator.
60
+
61
+ It does not close synchronous iterator with .return() if that iterator
62
+ yields rejected Promise, so finally blocks within such an iterator are
63
+ never executed.
64
+
65
+ In the application code developers can choose between async and sync
66
+ forms of the loop depending on their needs, but in the library code we
67
+ have to handle such edge cases properly and close iterators anyway.
68
+ */
47
69
try {
48
- const { value, done } = await iterator . next ( ) ;
49
- if ( done ) {
50
- readable . push ( null ) ;
51
- } else if ( readable . push ( await value ) ) {
52
- next ( ) ;
70
+ if ( iterable [ SymbolAsyncIterator ] ) {
71
+ for await ( const data of iterable ) {
72
+ if ( readable . destroyed ) return ;
73
+ if ( ! readable . push ( data ) ) {
74
+ await new Promise ( ( resolve ) => { onDataNeeded = resolve ; } ) ;
75
+ if ( readable . destroyed ) return ;
76
+ }
77
+ }
53
78
} else {
54
- reading = false ;
79
+ for ( const data of iterable ) {
80
+ const value = await data ;
81
+ if ( readable . destroyed ) return ;
82
+ if ( ! readable . push ( value ) ) {
83
+ await new Promise ( ( resolve ) => { onDataNeeded = resolve ; } ) ;
84
+ if ( readable . destroyed ) return ;
85
+ }
86
+ }
55
87
}
56
- } catch ( err ) {
57
- readable . destroy ( err ) ;
88
+ if ( ! readable . destroyed ) readable . push ( null ) ;
89
+ } catch ( error ) {
90
+ if ( ! readable . destroyed ) readable . destroy ( error ) ;
58
91
}
59
92
}
60
- return readable ;
61
93
}
62
94
63
95
module . exports = from ;
0 commit comments