Skip to content

Commit f2ba924

Browse files
committed
stream: implement ReadableStream.from
Fixes: nodejs#48389
1 parent 8c8e7e9 commit f2ba924

File tree

2 files changed

+126
-0
lines changed

2 files changed

+126
-0
lines changed

lib/internal/webstreams/readablestream.js

+74
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const {
66
ArrayBufferPrototypeSlice,
77
ArrayPrototypePush,
88
ArrayPrototypeShift,
9+
Boolean,
910
DataView,
1011
FunctionPrototypeBind,
1112
FunctionPrototypeCall,
@@ -110,6 +111,8 @@ const {
110111
nonOpCancel,
111112
nonOpPull,
112113
nonOpStart,
114+
getIterator,
115+
iteratorNext,
113116
kType,
114117
kState,
115118
} = require('internal/webstreams/util');
@@ -314,6 +317,10 @@ class ReadableStream {
314317
return isReadableStreamLocked(this);
315318
}
316319

320+
static from(iterable) {
321+
return readableStreamFromIterable(iterable);
322+
}
323+
317324
/**
318325
* @param {any} [reason]
319326
* @returns { Promise<void> }
@@ -1249,6 +1256,73 @@ const isReadableStreamBYOBReader =
12491256

12501257
// ---- ReadableStream Implementation
12511258

1259+
function readableStreamFromIterable(iterable) {
1260+
let stream;
1261+
const iteratorRecord = getIterator(iterable, 'async');
1262+
1263+
const startAlgorithm = nonOpStart;
1264+
1265+
function pullAlgorithm() {
1266+
let nextResult;
1267+
try {
1268+
nextResult = iteratorNext(iteratorRecord);
1269+
} catch (error) {
1270+
return PromiseReject(error);
1271+
}
1272+
const nextPromise = PromiseResolve(nextResult);
1273+
return PromisePrototypeThen(nextPromise, (iterResult) => {
1274+
if (typeof iterResult !== 'object' || iterResult === null) {
1275+
throw new ERR_INVALID_ARG_VALUE.TypeError('iterResult', iterResult);
1276+
}
1277+
const done = Boolean(iterResult.done);
1278+
if (done) {
1279+
readableStreamDefaultControllerClose(stream[kState].controller);
1280+
} else {
1281+
readableStreamDefaultControllerEnqueue(stream[kState].controller, iterResult.value);
1282+
}
1283+
});
1284+
}
1285+
1286+
function cancelAlgorithm(reason) {
1287+
const iterator = iteratorRecord.iterator;
1288+
let returnMethod;
1289+
try {
1290+
returnMethod = iterator.return;
1291+
} catch (error) {
1292+
return PromiseReject(error);
1293+
}
1294+
if (returnMethod === undefined) {
1295+
return PromiseResolve();
1296+
}
1297+
let returnResult;
1298+
try {
1299+
returnResult = FunctionPrototypeCall(returnMethod, iterator, [ reason ]);
1300+
} catch (error) {
1301+
return PromiseReject(error);
1302+
}
1303+
const returnPromise = PromiseResolve(returnResult);
1304+
return PromisePrototypeThen(returnPromise, (iterResult) => {
1305+
if (typeof iterResult !== 'object' || iterResult === null) {
1306+
throw new ERR_INVALID_ARG_VALUE.TypeError('iterResult', iterResult);
1307+
}
1308+
return undefined;
1309+
});
1310+
}
1311+
1312+
stream = new ReadableStream({
1313+
start: startAlgorithm,
1314+
pull: pullAlgorithm,
1315+
cancel: cancelAlgorithm,
1316+
}, {
1317+
size() {
1318+
return 1;
1319+
},
1320+
highWaterMark: 0,
1321+
});
1322+
1323+
return stream;
1324+
}
1325+
12521326
function readableStreamPipeTo(
12531327
source,
12541328
dest,

lib/internal/webstreams/util.js

+52
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ const {
1313
PromiseReject,
1414
ReflectGet,
1515
Symbol,
16+
SymbolAsyncIterator,
17+
SymbolIterator,
1618
Uint8Array,
1719
} = primordials;
1820

@@ -217,6 +219,54 @@ function lazyTransfer() {
217219
return transfer;
218220
}
219221

222+
function createAsyncFromSyncIterator(syncIteratorRecord) {
223+
const syncIterable = {
224+
[SymbolIterator]: () => syncIteratorRecord.iterator,
225+
};
226+
227+
const asyncIterator = (async function* () {
228+
return yield* syncIterable;
229+
}());
230+
231+
const nextMethod = asyncIterator.next;
232+
return { iterator: asyncIterator, nextMethod, done: false };
233+
}
234+
235+
function getIterator(obj, kind = 'sync', method) {
236+
if (method === undefined) {
237+
if (kind === 'async') {
238+
method = obj[SymbolAsyncIterator];
239+
if (method === undefined) {
240+
const syncMethod = obj[SymbolIterator];
241+
const syncIteratorRecord = getIterator(obj, 'sync', syncMethod);
242+
return createAsyncFromSyncIterator(syncIteratorRecord);
243+
}
244+
} else {
245+
method = obj[SymbolIterator];
246+
}
247+
}
248+
249+
const iterator = FunctionPrototypeCall(method, obj);
250+
if (typeof iterator !== 'object' || iterator === null) {
251+
throw new ERR_INVALID_ARG_VALUE.TypeError('iterator', iterator);
252+
}
253+
const nextMethod = iterator.next;
254+
return { iterator, nextMethod, done: false };
255+
}
256+
257+
function iteratorNext(iteratorRecord, value) {
258+
let result;
259+
if (value === undefined) {
260+
result = FunctionPrototypeCall(iteratorRecord.nextMethod, iteratorRecord.iterator);
261+
} else {
262+
result = FunctionPrototypeCall(iteratorRecord.nextMethod, iteratorRecord.iterator, [value]);
263+
}
264+
if (typeof result !== 'object' || result === null) {
265+
throw new ERR_INVALID_ARG_VALUE.TypeError('iterator.next', result);
266+
}
267+
return result;
268+
}
269+
220270
module.exports = {
221271
ArrayBufferViewGetBuffer,
222272
ArrayBufferViewGetByteLength,
@@ -243,6 +293,8 @@ module.exports = {
243293
nonOpPull,
244294
nonOpStart,
245295
nonOpWrite,
296+
getIterator,
297+
iteratorNext,
246298
kType,
247299
kState,
248300
};

0 commit comments

Comments
 (0)