-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into docs-update
- Loading branch information
Showing
8 changed files
with
316 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@apollo/client": patch | ||
--- | ||
|
||
Fixes a race condition in asyncMap that caused issues in React Native when errors were returned in the response payload along with a data property that was null. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import type { Observable } from "../../utilities/index.js"; | ||
|
||
interface TakeOptions { | ||
timeout?: number; | ||
} | ||
type ObservableEvent<T> = | ||
| { type: "next"; value: T } | ||
| { type: "error"; error: any } | ||
| { type: "complete" }; | ||
|
||
async function* observableToAsyncEventIterator<T>(observable: Observable<T>) { | ||
let resolveNext: (value: ObservableEvent<T>) => void; | ||
const promises: Promise<ObservableEvent<T>>[] = []; | ||
queuePromise(); | ||
|
||
function queuePromise() { | ||
promises.push( | ||
new Promise<ObservableEvent<T>>((resolve) => { | ||
resolveNext = (event: ObservableEvent<T>) => { | ||
resolve(event); | ||
queuePromise(); | ||
}; | ||
}) | ||
); | ||
} | ||
|
||
observable.subscribe( | ||
(value) => resolveNext({ type: "next", value }), | ||
(error) => resolveNext({ type: "error", error }), | ||
() => resolveNext({ type: "complete" }) | ||
); | ||
|
||
while (true) { | ||
yield promises.shift()!; | ||
} | ||
} | ||
|
||
class IteratorStream<T> { | ||
constructor(private iterator: AsyncGenerator<T, void, unknown>) {} | ||
|
||
async take({ timeout = 100 }: TakeOptions = {}): Promise<T> { | ||
return Promise.race([ | ||
this.iterator.next().then((result) => result.value!), | ||
new Promise<T>((_, reject) => { | ||
setTimeout( | ||
reject, | ||
timeout, | ||
new Error("Timeout waiting for next event") | ||
); | ||
}), | ||
]); | ||
} | ||
} | ||
|
||
export class ObservableStream<T> extends IteratorStream<ObservableEvent<T>> { | ||
constructor(observable: Observable<T>) { | ||
super(observableToAsyncEventIterator(observable)); | ||
} | ||
|
||
async takeNext(options?: TakeOptions): Promise<T> { | ||
const event = await this.take(options); | ||
expect(event).toEqual({ type: "next", value: expect.anything() }); | ||
return (event as ObservableEvent<T> & { type: "next" }).value; | ||
} | ||
|
||
async takeError(options?: TakeOptions): Promise<any> { | ||
const event = await this.take(options); | ||
expect(event).toEqual({ type: "error", error: expect.anything() }); | ||
return (event as ObservableEvent<T> & { type: "error" }).error; | ||
} | ||
|
||
async takeComplete(options?: TakeOptions): Promise<void> { | ||
const event = await this.take(options); | ||
expect(event).toEqual({ type: "complete" }); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { Observable } from "../../../utilities"; | ||
import { ObservableStream } from "../ObservableStream"; | ||
|
||
it("allows to step through an observable until completion", async () => { | ||
const stream = new ObservableStream( | ||
new Observable<number>((observer) => { | ||
observer.next(1); | ||
observer.next(2); | ||
observer.next(3); | ||
observer.complete(); | ||
}) | ||
); | ||
await expect(stream.takeNext()).resolves.toBe(1); | ||
await expect(stream.takeNext()).resolves.toBe(2); | ||
await expect(stream.takeNext()).resolves.toBe(3); | ||
await expect(stream.takeComplete()).resolves.toBeUndefined(); | ||
}); | ||
|
||
it("allows to step through an observable until error", async () => { | ||
const stream = new ObservableStream( | ||
new Observable<number>((observer) => { | ||
observer.next(1); | ||
observer.next(2); | ||
observer.next(3); | ||
observer.error(new Error("expected")); | ||
}) | ||
); | ||
await expect(stream.takeNext()).resolves.toBe(1); | ||
await expect(stream.takeNext()).resolves.toBe(2); | ||
await expect(stream.takeNext()).resolves.toBe(3); | ||
await expect(stream.takeError()).resolves.toEqual(expect.any(Error)); | ||
}); | ||
|
||
it("will time out if no more value is omitted", async () => { | ||
const stream = new ObservableStream( | ||
new Observable<number>((observer) => { | ||
observer.next(1); | ||
observer.next(2); | ||
}) | ||
); | ||
await expect(stream.takeNext()).resolves.toBe(1); | ||
await expect(stream.takeNext()).resolves.toBe(2); | ||
await expect(stream.takeNext()).rejects.toEqual(expect.any(Error)); | ||
}); | ||
|
||
it.each([ | ||
["takeNext", "complete"], | ||
["takeNext", "error"], | ||
["takeError", "complete"], | ||
["takeError", "next"], | ||
["takeComplete", "next"], | ||
["takeComplete", "error"], | ||
])("errors when %s receives %s instead", async (expected, gotten) => { | ||
const stream = new ObservableStream( | ||
new Observable<number>((observer) => { | ||
observer.next(1); | ||
observer.next(2); | ||
// @ts-ignore | ||
observer[gotten](3); | ||
}) | ||
); | ||
await expect(stream.takeNext()).resolves.toBe(1); | ||
await expect(stream.takeNext()).resolves.toBe(2); | ||
// @ts-ignore | ||
await expect(stream[expected]()).rejects.toEqual(expect.any(Error)); | ||
}); | ||
|
||
it.each([ | ||
["takeNext", "next"], | ||
["takeError", "error"], | ||
["takeComplete", "complete"], | ||
])("succeeds when %s, receives %s", async (expected, gotten) => { | ||
const stream = new ObservableStream( | ||
new Observable<number>((observer) => { | ||
observer.next(1); | ||
observer.next(2); | ||
// @ts-ignore | ||
observer[gotten](3); | ||
}) | ||
); | ||
await expect(stream.takeNext()).resolves.toBe(1); | ||
await expect(stream.takeNext()).resolves.toBe(2); | ||
// @ts-ignore this should just not throw | ||
await stream[expected](); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from "./profile/index.js"; | ||
export * from "./disposables/index.js"; | ||
export { ObservableStream } from "./ObservableStream.js"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.