Skip to content

Commit b9ea196

Browse files
Await promises from sync iterators with for-await (#13824)
Co-authored-by: Mickey Rose <[email protected]>
1 parent 6029252 commit b9ea196

14 files changed

Lines changed: 186 additions & 41 deletions

File tree

packages/babel-helpers/src/helpers-generated.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@
55

66
import template from "@babel/template";
77

8+
export const asyncIterator = {
9+
minVersion: "7.15.9",
10+
ast: () =>
11+
template.program.ast(
12+
'\nexport default function _asyncIterator(iterable) {\n var method, async, sync;\n if (typeof Symbol !== "undefined") {\n async = Symbol.asyncIterator;\n sync = Symbol.iterator;\n }\n do {\n if (!sync) {\n async = "@@asyncIterator";\n sync = "@@iterator";\n }\n if (async && (method = iterable[async]) != null) {\n return method.call(iterable);\n }\n if ((method = iterable[sync]) != null) {\n return new AsyncFromSyncIterator(method.call(iterable));\n }\n } while (!(sync === "@@iterator" || (sync = null)));\n throw new TypeError("Object is not async iterable");\n}\nfunction AsyncFromSyncIterator(s) {\n AsyncFromSyncIterator = function (s) {\n this.s = s;\n this.n = s.next;\n };\n AsyncFromSyncIterator.prototype = {\n s: null,\n n: null,\n next: function () {\n return AsyncFromSyncIteratorContinuation(this.n.apply(this.s, arguments));\n },\n return: function (value) {\n var ret = this.s.return;\n if (ret === undefined) {\n return Promise.resolve({ value: value, done: true });\n }\n return AsyncFromSyncIteratorContinuation(ret.apply(this.s, arguments));\n },\n throw: function (value) {\n var thr = this.s.return;\n if (thr === undefined) return Promise.reject(value);\n return AsyncFromSyncIteratorContinuation(thr.apply(this.s, arguments));\n },\n };\n function AsyncFromSyncIteratorContinuation(r) {\n \n if (Object(r) !== r) {\n return Promise.reject(new TypeError(r + " is not an object."));\n }\n var done = r.done;\n return Promise.resolve(r.value).then(function (value) {\n return { value: value, done: done };\n });\n }\n return new AsyncFromSyncIterator(s);\n}\n',
13+
),
14+
};
15+
816
export const jsx = {
917
minVersion: "7.0.0-beta.0",
1018
ast: () =>

packages/babel-helpers/src/helpers.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,6 @@ const helper = (minVersion: string) => (tpl: TemplateStringsArray) => ({
1616
ast: () => template.program.ast(tpl),
1717
});
1818

19-
helpers.asyncIterator = helper("7.0.0-beta.0")`
20-
export default function _asyncIterator(iterable) {
21-
var method;
22-
if (typeof Symbol !== "undefined") {
23-
if (Symbol.asyncIterator) method = iterable[Symbol.asyncIterator];
24-
if (method == null && Symbol.iterator) method = iterable[Symbol.iterator];
25-
}
26-
if (method == null) method = iterable["@@asyncIterator"];
27-
if (method == null) method = iterable["@@iterator"]
28-
if (method == null) throw new TypeError("Object is not async iterable");
29-
return method.call(iterable);
30-
}
31-
`;
32-
3319
helpers.AwaitValue = helper("7.0.0-beta.0")`
3420
export default function _AwaitValue(value) {
3521
this.wrapped = value;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/* @minVersion 7.15.9 */
2+
3+
export default function _asyncIterator(iterable) {
4+
var method,
5+
async,
6+
sync,
7+
retry = 2;
8+
9+
if (typeof Symbol !== "undefined") {
10+
async = Symbol.asyncIterator;
11+
sync = Symbol.iterator;
12+
}
13+
14+
while (retry--) {
15+
if (async && (method = iterable[async]) != null) {
16+
return method.call(iterable);
17+
}
18+
if (sync && (method = iterable[sync]) != null) {
19+
return new AsyncFromSyncIterator(method.call(iterable));
20+
}
21+
22+
async = "@@asyncIterator";
23+
sync = "@@iterator";
24+
}
25+
26+
throw new TypeError("Object is not async iterable");
27+
}
28+
29+
function AsyncFromSyncIterator(s) {
30+
AsyncFromSyncIterator = function (s) {
31+
this.s = s;
32+
this.n = s.next;
33+
};
34+
AsyncFromSyncIterator.prototype = {
35+
/* SyncIterator */ s: null,
36+
/* SyncIterator.[[Next]] */ n: null,
37+
next: function () {
38+
return AsyncFromSyncIteratorContinuation(this.n.apply(this.s, arguments));
39+
},
40+
return: function (value) {
41+
var ret = this.s.return;
42+
if (ret === undefined) {
43+
return Promise.resolve({ value: value, done: true });
44+
}
45+
return AsyncFromSyncIteratorContinuation(ret.apply(this.s, arguments));
46+
},
47+
throw: function (value) {
48+
var thr = this.s.return;
49+
if (thr === undefined) return Promise.reject(value);
50+
return AsyncFromSyncIteratorContinuation(thr.apply(this.s, arguments));
51+
},
52+
};
53+
54+
function AsyncFromSyncIteratorContinuation(r) {
55+
// This step is _before_ calling AsyncFromSyncIteratorContinuation in the spec.
56+
if (Object(r) !== r) {
57+
return Promise.reject(new TypeError(r + " is not an object."));
58+
}
59+
60+
var done = r.done;
61+
return Promise.resolve(r.value).then(function (value) {
62+
return { value: value, done: done };
63+
});
64+
}
65+
66+
return new AsyncFromSyncIterator(s);
67+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
async function* fn() {
2+
for await (const result of [Promise.resolve("ok")]) {
3+
return { result };
4+
}
5+
}
6+
7+
return fn().next().then(result => {
8+
expect(result).toEqual({
9+
done: true,
10+
value: { result: "ok" }
11+
});
12+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
async function* fn() {
2+
for await (const result of [Promise.resolve("ok")]) {
3+
return { result };
4+
}
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"plugins": ["proposal-async-generator-functions"],
3+
"parserOpts": {
4+
"allowReturnOutsideFunction": true
5+
}
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
function fn() {
2+
return _fn.apply(this, arguments);
3+
}
4+
5+
function _fn() {
6+
_fn = babelHelpers.wrapAsyncGenerator(function* () {
7+
var _iteratorAbruptCompletion = false;
8+
var _didIteratorError = false;
9+
10+
var _iteratorError;
11+
12+
try {
13+
for (var _iterator = babelHelpers.asyncIterator([Promise.resolve("ok")]), _step; _iteratorAbruptCompletion = !(_step = yield babelHelpers.awaitAsyncGenerator(_iterator.next())).done; _iteratorAbruptCompletion = false) {
14+
const result = _step.value;
15+
return {
16+
result
17+
};
18+
}
19+
} catch (err) {
20+
_didIteratorError = true;
21+
_iteratorError = err;
22+
} finally {
23+
try {
24+
if (_iteratorAbruptCompletion && _iterator.return != null) {
25+
yield babelHelpers.awaitAsyncGenerator(_iterator.return());
26+
}
27+
} finally {
28+
if (_didIteratorError) {
29+
throw _iteratorError;
30+
}
31+
}
32+
}
33+
});
34+
return _fn.apply(this, arguments);
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
async function* fn() {
2+
yield* [Promise.resolve("ok")] // CreateAsyncFromSyncIterator
3+
}
4+
5+
return fn().next().then(result => {
6+
expect(result).toEqual({ value: "ok", done: false });
7+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
async function* fn() {
2+
yield* [Promise.resolve("ok")] // CreateAsyncFromSyncIterator
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"plugins": ["proposal-async-generator-functions"],
3+
"parserOpts": {
4+
"allowReturnOutsideFunction": true
5+
}
6+
}

0 commit comments

Comments
 (0)