You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hard wrap lines.
Consistently use function names without formatting.
Interleave error code examples with explanations.
Add separation lines within code blocks.
Link to Zalgo article and comment sources.
Remove note about plenary discussion of array-likes.
Make more reference links.
for **dumping** the entirety of an **async iterator**
23
-
into a **single data structure**,
24
-
especially in **unit tests** or in **command-line** interfaces.
25
-
(Several real-world examples are included in a following section.)
20
+
Such functionality would be useful for **dumping** the entirety of an **async
21
+
iterator** into a **single data structure**, especially in **unit tests** or in
22
+
**command-line** interfaces. (Several real-world examples are included in a
23
+
following section.)
26
24
27
25
There is an [it-all][] NPM library that performs only this task
28
26
and which gets about 50,000 weekly downloads daily.
@@ -47,54 +45,75 @@ later in this explainer.
47
45
## Description
48
46
(A [formal draft specification][specification] is available.)
49
47
50
-
### Async-iterable inputs
51
-
Similarly to **[`Array.from`][]**,
52
-
**`Array.fromAsync`** would be a **static method**
53
-
of the `Array` built-in class, with **one required argument**
54
-
and **two optional arguments**: `(items, mapfn, thisArg)`.
48
+
Array.fromAsync is to `for await`\
49
+
as Array.from is to `for`.
50
+
51
+
Similarly to [Array.from][], Array.fromAsync would be a static method of the
52
+
`Array` built-in class, with one required argument and two optional arguments:
53
+
`(items, mapfn, thisArg)`.
55
54
56
-
But instead of converting an **array-like object** or **iterable** to an array,
57
-
it converts an **async iterable** (or array-like object or iterable)
58
-
to a **promise** that will resolve to an array.
55
+
### Async-iterable inputs
56
+
But, instead of converting a sync iterable to an array, Array.fromAsync can
57
+
convert an async iterable to a **promise** that (if everything goes well) will
58
+
resolve to a new array. Before the promise resolves, it will create an async
59
+
iterator from the input, lazily iterate over it, and add each yielded value to
60
+
the new array. (The promise is immediately returned after the Array.fromAsync
61
+
function call, no matter what.)
59
62
60
63
```js
61
64
asyncfunction*asyncGen (n) {
62
65
for (let i =0; i < n; i++)
63
66
yield i *2;
64
67
}
65
-
// arr will be [0, 2, 4, 6].
68
+
69
+
// `arr` will be `[0, 2, 4, 6]`.
66
70
constarr= [];
67
71
forawait (constvofasyncGen(4)) {
68
72
arr.push(v);
69
73
}
74
+
70
75
// This is equivalent.
71
76
constarr=awaitArray.fromAsync(asyncGen(4));
72
77
```
73
78
74
79
### Sync-iterable inputs
75
-
If the argument is a sync iterable (and not an async iterable), then the return value is still a promise that will resolve to an array.
76
-
If the sync iterator yields promises, then each yielded promise is awaited before its value is added to the new array. (Values that are not promises are also awaited for one microtick to prevent Zalgo.)
77
-
This matches the behavior of `for await`.
80
+
If the argument is a sync iterable (and not an async iterable), then the return
81
+
value is still a promise that will resolve to an array. If the sync iterator
82
+
yields promises, then each yielded promise is awaited before its value is added
83
+
to the new array. (Values that are not promises are also awaited for one
84
+
microtick to [prevent Zalgo][Zalgo].) All of this matches the behavior of `for
Array.fromAsync’s valid inputs are a superset of Array.from’s valid inputs. This includes non-iterable array-likes: objects that have a length property as well as indexed elements.
95
-
The return value is still a promise that will resolve to an array.
96
-
If the array-like object’s elements are promises, then each accessed promise is awaited before its value is added to the new array.
97
-
One TC39 representative’s opinion: “[Array-likes are] very much not obsolete, and it’s very nice that things aren’t forced to implement the iterator protocol to be transformable into an Array.”
106
+
Array.fromAsync’s valid inputs are a superset of Array.from’s valid inputs. This
107
+
includes non-iterable array-likes: objects that have a length property as well
108
+
as indexed elements. The return value is still a promise that will resolve to an
109
+
array. If the array-like object’s elements are promises, then each accessed
110
+
promise is awaited before its value is added to the new array.
111
+
112
+
One [TC39 representative’s opinion][issue #7 comment]: “[Array-likes are] very
113
+
much not obsolete, and it’s very nice that things aren’t forced to implement the
114
+
iterator protocol to be transformable into an Array.”
See issue #7. Previously discussed at 2021-11 plenary without objections.
115
135
```
116
136
117
137
### Generic factory method
118
-
Array.fromAsync is a generic factory method. It does not require that its this receiver be the Array constructor.
119
-
fromAsync can be transferred to or inherited by any other constructor with a single numeric parameter. In that case, the final result will be the data structure created by that constructor (with 0 as its argument), and with each value yielded by the input being assigned to the data structure’s numeric properties.
120
-
(Symbol.species is not involved at all.)
121
-
If the this receiver is not a constructor, then fromAsync creates an array as usual.
122
-
This matches the behavior of Array.from.
138
+
Array.fromAsync is a generic factory method. It does not require that its this
139
+
receiver be the Array constructor. fromAsync can be transferred to or inherited
140
+
by any other constructor with a single numeric parameter. In that case, the
141
+
final result will be the data structure created by that constructor (with 0 as
142
+
its argument), and with each value yielded by the input being assigned to the
143
+
data structure’s numeric properties. (Symbol.species is not involved at all.) If
144
+
the this receiver is not a constructor, then fromAsync creates an array as
145
+
usual. This matches the behavior of Array.from.
123
146
124
147
```js
125
148
asyncfunction*asyncGen (n) {
@@ -129,61 +152,120 @@ async function * asyncGen (n) {
129
152
functionData (n) {}
130
153
Data.from=Array.from;
131
154
Data.fromAsync=Array.fromAsync;
132
-
// d will be a new Data(0), with
133
-
// 0 assigned to 0, 1 assigned to 2, etc.
155
+
156
+
// d will be a `new Data(0)`, with its `0` property assigned to `0`, its `1`
157
+
// property assigned to `2`, etc.
134
158
constd=newData(0); let i =0;
135
159
forawait (constvofasyncGen(4)) {
136
160
d[i] = v;
137
161
}
162
+
138
163
// This is equivalent.
139
164
constd=awaitData.fromAsync(asyncGen(4));
140
165
```
141
166
142
167
### Optional parameters
143
-
Array.fromAsync has two optional parameters.
144
-
The first optional parameter is a mapping callback, which is called on each value yielded from the input – the result of which is awaited then added to the array.
145
-
Unlike `Array.from`, `mapfn` may be an async function.)
146
-
By default, this is essentially an identity function.
147
-
The second optional parameter is a this value for the mapping callback. By default, this is undefined.
148
-
These optional parameters match the behavior of Array.from. Their exclusion would be surprising to developers who are already used to Array.from.
168
+
Array.fromAsync has two optional parameters: `mapfn` and `thisArg`.
169
+
170
+
`mapfn` is a mapping callback, which is called on each value yielded from the
171
+
input – the result of which is awaited then added to the array. Unlike
172
+
Array.from, `mapfn` may be an async function.) By default, this is essentially
173
+
an identity function.
174
+
175
+
`thisArg` is a `this`-binding receiver value for the mapping callback. By
176
+
default, this is undefined. These optional parameters match the behavior of
177
+
Array.from. Their exclusion would be surprising to developers who are already
178
+
used to Array.from.
149
179
150
180
```js
151
181
asyncfunction*asyncGen (n) {
152
182
for (let i =0; i < n; i++)
153
183
yield i *2;
154
184
}
155
-
// arr will be [0, 4, 16, 36].
185
+
186
+
// `arr` will be `[ 0, 4, 16, 36 ]`.
156
187
constarr= [];
157
188
forawait (constvofasyncGen(4)) {
158
189
arr.push(v **2);
159
190
}
191
+
160
192
// This is equivalent.
161
-
constarr=awaitArray.fromAsync(asyncGen(4),
162
-
v=> v **2);
193
+
constarr=awaitArray.fromAsync(asyncGen(4),v=>
194
+
v **2);
163
195
```
164
196
165
197
### Errors
166
-
Like other promise-based APIs, Array.fromAsync will always immediately return a promise. It will never synchronously throw an error and summon Zalgo.
167
-
If its input throws an error while creating its async or sync iterator, then its promise will reject with that error.
168
-
If its input’s iterator throws an error while yielding a value, then its promise will reject with that error.
169
-
If its this receiver’s constructor throws an error, then its promise will reject to that error.
170
-
If its mapping callback throws an error when given an input value, then its promise will reject with that error.
171
-
If its input is null or undefined, or if its mapping callback is neither undefined nor callable, then its promise will reject with a TypeError.
198
+
Like other promise-based APIs, Array.fromAsync will always immediately return a
199
+
promise. Array.fromAsync will never synchronously throw an error and [summon
200
+
Zalgo][Zalgo].
201
+
202
+
If Array.fromAsync’s input throws an error while creating its async or sync
203
+
iterator, then Array.fromAsync’s returned promise will reject with that error.
// This creates a promise that will reject with `err`.
249
+
Array.fromAsync(arrLikeWithRejection);
250
+
```
251
+
252
+
If Array.fromAsync’s input has at least one value, and Array.fromAsync’s mapping
253
+
callback throws an error when given any of those values, then Array.fromAsync’s
254
+
returned promise will reject with the first such error.
255
+
256
+
```js
257
+
consterr=newError;
258
+
functionbadCallback () { throw err; }
259
+
260
+
// This creates a promise that will reject with `err`.
261
+
Array.fromAsync([ 0 ], badCallback);
262
+
```
263
+
264
+
If Array.fromAsync’s input is null or undefined, or if Array.fromAsync’s mapping
265
+
callback is neither undefined nor callable, then Array.fromAsync’s returned
266
+
promise will reject with a TypeError.
267
+
268
+
```js
187
269
// These create promises that will reject with TypeErrors.
188
270
Array.fromAsync(null);
189
271
Array.fromAsync([], 1);
@@ -192,7 +274,8 @@ Array.fromAsync([], 1);
192
274
## Other proposals
193
275
194
276
### Relationship with iterator-helpers
195
-
The [iterator-helpers][] proposal has toArray, which works with both sync and async iterables.
277
+
The [iterator-helpers][] proposal has toArray, which works with both sync and
278
+
async iterables.
196
279
197
280
```js
198
281
Array.from(gen())
@@ -201,36 +284,46 @@ Array.fromAsync(asyncGen())
201
284
asyncGen().toArray()
202
285
```
203
286
204
-
toArray overlaps with both Array.from and Array.fromAsync. This is okay. They can coexist.
205
-
If we have to choose between having toArray and having fromAsync, then we should choose fromAsync. We already have Array.from. We should match the existing language precedent.
287
+
toArray overlaps with both Array.from and Array.fromAsync. This is okay. They
288
+
can coexist. If we have to choose between having toArray and having fromAsync,
289
+
then we should choose fromAsync. We already have Array.from. We should match the
290
+
existing language precedent.
206
291
207
-
See [tc39/proposal-iterator-helpers#156](https://github.com/tc39/proposal-iterator-helpers/issues/156).
208
-
A co-champion of iterable-helpers seems to agree that we should have both or that we should prefer Array.fromAsync:
209
-
“I remembered why it’s better for a buildable structure to consume an iterable than for an iterable to consume a buildable protocol. Sometimes building something one element at a time is the same as building it [more than one] element at a time, but sometimes it could be slow to build that way or produce a structure with equivalent semantics but different performance properties.”
292
+
A [co-champion of iterable-helpers agrees][tc39/proposal-iterator-helpers#156]
293
+
that we should have both or that we should prefer Array.fromAsync: “I remembered
294
+
why it’s better for a buildable structure to consume an iterable than for an
295
+
iterable to consume a buildable protocol. Sometimes building something one
296
+
element at a time is the same as building it [more than one] element at a time,
297
+
but sometimes it could be slow to build that way or produce a structure with
298
+
equivalent semantics but different performance properties.”
### TypedArray.fromAsync, Set.fromAsync, Object.fromEntriesAsync, etc.
214
304
The following built-ins also resemble Array.from:
215
305
```js
216
-
TypedArray.from
306
+
TypedArray.from()
217
307
newSet
218
-
Object.fromEntries
308
+
Object.fromEntries()
219
309
newMap
220
310
```
221
311
We are deferring any async versions of these methods to future proposals.
222
-
See [issue #8](https://github.com/tc39/proposal-array-from-async/issues/8) and [proposal-setmap-offrom](https://github.com/tc39/proposal-setmap-offrom).
0 commit comments