stream: update ongoing promise in async iterator return() method

PR-URL: https://github.com/nodejs/node/pull/52657
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Debadree Chatterjee <debadree333@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Mattias Buelens 2024-04-28 18:51:44 +02:00 committed by GitHub
parent 1aab22e305
commit eee9db8b8e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 137 additions and 5 deletions

View File

@ -545,12 +545,14 @@ class ReadableStream {
},
return(error) {
return state.current ?
started = true;
state.current = state.current !== undefined ?
PromisePrototypeThen(
state.current,
() => returnSteps(error),
() => returnSteps(error)) :
returnSteps(error);
return state.current;
},
[SymbolAsyncIterator]() { return this; },

View File

@ -27,7 +27,7 @@ Last update:
- performance-timeline: https://github.com/web-platform-tests/wpt/tree/17ebc3aea0/performance-timeline
- resource-timing: https://github.com/web-platform-tests/wpt/tree/22d38586d0/resource-timing
- resources: https://github.com/web-platform-tests/wpt/tree/1e140d63ec/resources
- streams: https://github.com/web-platform-tests/wpt/tree/3df6d94318/streams
- streams: https://github.com/web-platform-tests/wpt/tree/9b03282a99/streams
- url: https://github.com/web-platform-tests/wpt/tree/0f550ab9f5/url
- user-timing: https://github.com/web-platform-tests/wpt/tree/5ae85bf826/user-timing
- wasm/jsapi: https://github.com/web-platform-tests/wpt/tree/cde25e7e3c/wasm/jsapi

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<script type="module">
let a = self.open()
let d = await a.navigator.storage.getDirectory()
let h = await d.getFileHandle("c5c9960b-a637-4232-be3d-3ccc5704852f", {"create": true})
let r = new ReadableStream({
start(c) {
c.enqueue(h)
c.close();
}
});
let w = await h.createWritable({ })
r.pipeThrough({"readable": r, "writable": w}, {})
</script>

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<body>
<script>
window.onload = () => {
const i = document.createElement("iframe");
i.src = "about:blank";
document.body.appendChild(i);
const rs = new i.contentWindow.ReadableStream({
start(controller) { controller.error(); }
});
const ws = new i.contentWindow.WritableStream();
i.remove();
// pipeTo() should not crash with a ReadableStream or WritableStream from
// a detached iframe.
rs.pipeTo(ws);
};
</script>
</body>

View File

@ -475,15 +475,85 @@ promise_test(async () => {
const rs = new ReadableStream();
const it = rs.values();
const iterResults = await Promise.allSettled([it.return('return value'), it.next()]);
const resolveOrder = [];
const iterResults = await Promise.allSettled([
it.return('return value').then(result => {
resolveOrder.push('return');
return result;
}),
it.next().then(result => {
resolveOrder.push('next');
return result;
})
]);
assert_equals(iterResults[0].status, 'fulfilled', 'return() promise status');
assert_iter_result(iterResults[0].value, 'return value', true, 'return()');
assert_equals(iterResults[1].status, 'fulfilled', 'next() promise status');
assert_iter_result(iterResults[1].value, undefined, true, 'next()');
assert_array_equals(resolveOrder, ['return', 'next'], 'next() resolves after return()');
}, 'return(); next() [no awaiting]');
promise_test(async () => {
let resolveCancelPromise;
const rs = recordingReadableStream({
cancel(reason) {
return new Promise(r => resolveCancelPromise = r);
}
});
const it = rs.values();
let returnResolved = false;
const returnPromise = it.return('return value').then(result => {
returnResolved = true;
return result;
});
await flushAsyncEvents();
assert_false(returnResolved, 'return() should not resolve while cancel() promise is pending');
resolveCancelPromise();
const iterResult1 = await returnPromise;
assert_iter_result(iterResult1, 'return value', true, 'return()');
const iterResult2 = await it.next();
assert_iter_result(iterResult2, undefined, true, 'next()');
}, 'return(); next() with delayed cancel()');
promise_test(async () => {
let resolveCancelPromise;
const rs = recordingReadableStream({
cancel(reason) {
return new Promise(r => resolveCancelPromise = r);
}
});
const it = rs.values();
const resolveOrder = [];
const returnPromise = it.return('return value').then(result => {
resolveOrder.push('return');
return result;
});
const nextPromise = it.next().then(result => {
resolveOrder.push('next');
return result;
});
assert_array_equals(rs.events, ['cancel', 'return value'], 'return() should call cancel()');
assert_array_equals(resolveOrder, [], 'return() should not resolve before cancel() resolves');
resolveCancelPromise();
const iterResult1 = await returnPromise;
assert_iter_result(iterResult1, 'return value', true, 'return() should resolve with original reason');
const iterResult2 = await nextPromise;
assert_iter_result(iterResult2, undefined, true, 'next() should resolve with done result');
assert_array_equals(rs.events, ['cancel', 'return value'], 'no pull() after cancel()');
assert_array_equals(resolveOrder, ['return', 'next'], 'next() should resolve after return() resolves');
}, 'return(); next() with delayed cancel() [no awaiting]');
promise_test(async () => {
const rs = new ReadableStream();
const it = rs.values();
@ -499,13 +569,25 @@ promise_test(async () => {
const rs = new ReadableStream();
const it = rs.values();
const iterResults = await Promise.allSettled([it.return('return value 1'), it.return('return value 2')]);
const resolveOrder = [];
const iterResults = await Promise.allSettled([
it.return('return value 1').then(result => {
resolveOrder.push('return 1');
return result;
}),
it.return('return value 2').then(result => {
resolveOrder.push('return 2');
return result;
})
]);
assert_equals(iterResults[0].status, 'fulfilled', '1st return() promise status');
assert_iter_result(iterResults[0].value, 'return value 1', true, '1st return()');
assert_equals(iterResults[1].status, 'fulfilled', '2nd return() promise status');
assert_iter_result(iterResults[1].value, 'return value 2', true, '1st return()');
assert_array_equals(resolveOrder, ['return 1', 'return 2'], '2nd return() resolves after 1st return()');
}, 'return(); return() [no awaiting]');
test(() => {

View File

@ -0,0 +1,13 @@
<!doctype html>
<body>
<script>
const i = document.createElement("iframe");
document.body.appendChild(i);
const rs = new i.contentWindow.ReadableStream();
i.remove();
// tee() on a ReadableStream from a detached iframe should not crash.
rs.tee();
</script>
</body>

View File

@ -68,7 +68,7 @@
"path": "resources"
},
"streams": {
"commit": "3df6d94318b225845a0c8e4c7718484f41c9b8ce",
"commit": "9b03282a99ef2314c1c2d5050a105a74a2940019",
"path": "streams"
},
"url": {