Skip to content

Commit bcf47bd

Browse files
DOM: Bring Observable iterable conversion inline with spec
This CL ensures that after `Observable.from()` is called with an iterable, if the resulting Observable fails to iterate when subscribed to, all errors are plumbed to the subscriber. This matches the spec PR in WICG/observable#160. R=masonf Bug: 363015168 Change-Id: I31a38d7f57ec7ab5b29388d908a6a58e0b3c1905 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6199726 Reviewed-by: Mason Freed <[email protected]> Commit-Queue: Dominic Farolino <[email protected]> Cr-Commit-Position: refs/heads/main@{#1411273}
1 parent e679be3 commit bcf47bd

File tree

1 file changed

+84
-23
lines changed

1 file changed

+84
-23
lines changed

dom/observable/tentative/observable-from.any.js

Lines changed: 84 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,14 @@ test(() => {
133133

134134
// This tests that once `Observable.from()` detects a non-null and non-undefined
135135
// `[Symbol.iterator]` property, we've committed to converting as an iterable.
136-
// If the value of that property is not callable, we don't silently move on to
137-
// the next conversion type — we throw a TypeError;
136+
// If the value of that property is then not callable, we don't silently move on
137+
// to the next conversion type — we throw a TypeError.
138+
//
139+
// That's because that's what TC39's `GetMethod()` [1] calls for, which is what
140+
// `Observable.from()` first uses in the iterable conversion branch [2].
141+
//
142+
// [1]: https://tc39.es/ecma262/multipage/abstract-operations.html#sec-getmethod
143+
// [2]: http://wicg.github.io/observable/#from-iterable-conversion
138144
test(() => {
139145
let results = [];
140146
const iterable = {
@@ -149,11 +155,84 @@ test(() => {
149155
}
150156

151157
assert_true(errorThrown instanceof TypeError);
152-
assert_equals(errorThrown.message,
153-
"Failed to execute 'from' on 'Observable': @@iterator must be a " +
154-
"callable.");
155158
}, "from(): [Symbol.iterator] not callable");
156159

160+
test(() => {
161+
let results = [];
162+
const iterable = {
163+
calledOnce: false,
164+
get [Symbol.iterator]() {
165+
if (this.calledOnce) {
166+
// Return a non-callable primitive the second time `@@iterator` is
167+
// called.
168+
return 10;
169+
}
170+
171+
this.calledOnce = true;
172+
return this.validImplementation;
173+
},
174+
validImplementation: () => {
175+
return {
176+
next() { return {done: true}; }
177+
}
178+
}
179+
};
180+
181+
let errorThrown = null;
182+
183+
const observable = Observable.from(iterable);
184+
observable.subscribe({
185+
next: v => results.push("should not be called"),
186+
error: e => {
187+
errorThrown = e;
188+
results.push(e);
189+
},
190+
});
191+
192+
assert_array_equals(results, [errorThrown],
193+
"An error was plumbed through the Observable");
194+
assert_true(errorThrown instanceof TypeError);
195+
}, "from(): [Symbol.iterator] not callable AFTER SUBSCRIBE throws");
196+
197+
test(() => {
198+
let results = [];
199+
const iterable = {
200+
calledOnce: false,
201+
validImplementation: () => {
202+
return {
203+
next() { return {done: true}; }
204+
}
205+
},
206+
get [Symbol.iterator]() {
207+
if (this.calledOnce) {
208+
// Return null the second time `@@iterator` is called.
209+
return null;
210+
}
211+
212+
this.calledOnce = true;
213+
return this.validImplementation;
214+
}
215+
};
216+
217+
let errorThrown = null;
218+
219+
const observable = Observable.from(iterable);
220+
observable.subscribe({
221+
next: v => results.push("should not be called"),
222+
error: e => {
223+
errorThrown = e;
224+
results.push(e);
225+
},
226+
});
227+
228+
assert_array_equals(results, [errorThrown],
229+
"An error was plumbed through the Observable");
230+
assert_true(errorThrown instanceof TypeError);
231+
assert_equals(errorThrown.message,
232+
"Failed to execute 'subscribe' on 'Observable': @@iterator must not be " +
233+
"undefined or null");
234+
}, "from(): [Symbol.iterator] returns null AFTER SUBSCRIBE throws");
235+
157236
test(() => {
158237
let results = [];
159238
const customError = new Error("@@iterator override error");
@@ -520,24 +599,6 @@ test(() => {
520599
}, "from(): Rethrows the error when Converting an object whose @@iterator " +
521600
"method *getter* throws an error");
522601

523-
test(() => {
524-
const obj = {};
525-
// Non-undefined & non-null values of the `@@iterator` property are not
526-
// allowed. Specifically they fail the the `IsCallable()` test, which fails
527-
// Observable conversion.
528-
obj[Symbol.iterator] = 10;
529-
530-
try {
531-
Observable.from(obj);
532-
assert_unreached("from() conversion throws");
533-
} catch(e) {
534-
assert_true(e instanceof TypeError);
535-
assert_equals(e.message,
536-
"Failed to execute 'from' on 'Observable': @@iterator must be a callable.");
537-
}
538-
}, "from(): Throws 'callable' error when @@iterator property is a " +
539-
"non-callable primitive");
540-
541602
// This test exercises the line of spec prose that says:
542603
//
543604
// "If |asyncIteratorMethodRecord|'s [[Value]] is undefined or null, then jump

0 commit comments

Comments
 (0)