-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(Observable): now implements Symbol.asyncIterator
#7189
Conversation
375c99c
to
e34aa5e
Compare
I suggest to play it safe and do it first as a regular method, and after a while do it with For example, the current implementation will trigger a microtask on each event, even if we already have events stored in the queue. In streams, we usually work not with individual items, but with their chunks for await (const events of source$) {
console.log(events); // event accumulated between iterations
} At the same time, nothing will interfere with us to work with each element separately. At the same time, it will not be unnecessary to provoke microtasks (and parasite Promise). for await (const events of source$) {
for (const event of events) {
console.log(event);
}
} I hope that I was able to convey my thought correctly. Sorry if I wrote inaudibly somewhere. English is not my bearing language. |
ce3072e
to
680f0d3
Compare
@demensky The library https://github.com/benlesh/rxjs-for-await as existed for quite some time. Every single use of that library I've found uses |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! I just left one question regarding the tests
spec/Observable-spec.ts
Outdated
it('should unsubscribe if the for-await-of loop is broken', async () => { | ||
const source = new Observable<number>((subscriber) => { | ||
subscriber.next(1); | ||
subscriber.next(2); | ||
subscriber.next(3); | ||
subscriber.complete(); | ||
}); | ||
|
||
const results: number[] = []; | ||
for await (const value of source) { | ||
results.push(value); | ||
break; | ||
} | ||
|
||
expect(results).to.deep.equal([1]); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either a test name is misleading here or the unsubscription is not tested here. Are we expecting the subscription to be unsubscribed earlier if we break out of the loop?
Based on the test name I would imagine something like this:
it('should unsubscribe if the for-await-of loop is broken', async () => {
let state = 'idle';
const source = new Observable<number>((subscriber) => {
subscriber.next(1);
setTimeout(() => subscriber.next(2), 100);
setTimeout(() => subscriber.next(3), 200);
setTimeout(() => subscriber.complete(), 500);
return () => {
state = 'unsubscribed'
}
});
expect(state).to.equal('idle')
const results: number[] = [];
for await (const value of source) {
results.push(value);
break;
}
expect(state).to.equal('unsubscribed')
expect(results).to.deep.equal([1]);
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bump, as I see what they're saying. Same comment for the other unsubscribe test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll change this one around to test an active subscription count.
throw: (err): Promise<IteratorResult<T>> => { | ||
subscription?.unsubscribe(); | ||
// NOTE: I did some research on this, and as of Feb 2023, Chrome doesn't seem to do | ||
// anything with pending promises returned from `next()` when `throw()` is called. | ||
// However, for consumption of observables, I don't want RxJS taking the heat for that | ||
// quirk/leak of the type. So we're going to reject all pending promises we've nexted out here. | ||
handleError(err); | ||
return Promise.reject(err); | ||
}, | ||
return: (): Promise<IteratorResult<T>> => { | ||
subscription?.unsubscribe(); | ||
// NOTE: I did some research on this, and as of Feb 2023, Chrome doesn't seem to do | ||
// anything with pending promises returned from `next()` when `throw()` is called. | ||
// However, for consumption of observables, I don't want RxJS taking the heat for that | ||
// quirk/leak of the type. So we're going to resolve all pending promises we've nexted out here. | ||
handleComplete(); | ||
return Promise.resolve({ value: undefined, done: true }); | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL
I didn't know about this part of the iteration protocol. This is great!
CORE TEAM: Approval. |
680f0d3
to
654bc0a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally good, but some points that I at least wanted to run by you.
spec/Observable-spec.ts
Outdated
it('should unsubscribe if the for-await-of loop is broken', async () => { | ||
const source = new Observable<number>((subscriber) => { | ||
subscriber.next(1); | ||
subscriber.next(2); | ||
subscriber.next(3); | ||
subscriber.complete(); | ||
}); | ||
|
||
const results: number[] = []; | ||
for await (const value of source) { | ||
results.push(value); | ||
break; | ||
} | ||
|
||
expect(results).to.deep.equal([1]); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bump, as I see what they're saying. Same comment for the other unsubscribe test.
spec/Observable-spec.ts
Outdated
results.push(value); | ||
} | ||
} catch (err: any) { | ||
expect(err.message).to.equal('wee'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since chai doesn't have a way to assert the number of expected assertions (like jest does) I think it's generally a good idea to try to avoid assertions that rely on a condition that the test itself is trying to verify. e.g. if it stops throwing any error at all, this test would still pass. It would only fail if it does throw an error but that error doesn't have { message: 'wee' }
.
Something like:
let expectedError: any;
try {
for await (const value of source) {
results.push(value);
}
} catch (err: any) {
expectedError = err;
}
expect(expectedError.message).to.equal('wee');
src/internal/Observable.ts
Outdated
subscription = this.subscribe({ | ||
next: (value) => { | ||
if (deferreds.length) { | ||
deferreds.shift()![0]({ value, done: false }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO this code is "fine" but I think it would be much clearer for someone who isn't super-super familiar with how all this works to use an intermediate variable for the resolve, and maybe even an object with field names instead of an array tuple.
e.g.
const { resolve } = deferreds.shift()!;
resolve({ value, done: false });
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A solid suggestion. Implemented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, only minor types issues that could be considered fixing.
Adds async iterator support to
Observable
!#6779
#6857