Skip to content
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

Fix synchronous suspense cases on client-side #506

Merged
merged 3 commits into from
Jan 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@
"react": ">= 16.8.0"
},
"dependencies": {
"react-wonka": "^2.0.0",
"react-wonka": "^2.0.1",
"scheduler": ">= 0.16.0",
"wonka": "^4.0.5"
"wonka": "^4.0.7"
}
}
4 changes: 3 additions & 1 deletion src/utils/toSuspenseSource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ it('throws a promise that resolves when the source emits a value', () => {
expect(promise).toBeInstanceOf(Promise);

next('test');
expect(result).toBe('test');

// The result came in asynchronously and the original source has ended
expect(result).toBe(undefined);

return promise.then(resolved => {
expect(resolved).toBe('test');
Expand Down
58 changes: 15 additions & 43 deletions src/utils/toSuspenseSource.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,20 @@
import { pipe, make, onPush, onEnd, subscribe, Source } from 'wonka';
import { pipe, share, onPush, toPromise, takeWhile, take, Source } from 'wonka';

/** This converts a Source to a suspense Source; It will forward the first result synchronously or throw a promise that resolves when the result becomes available */
export const toSuspenseSource = <T>(source: Source<T>): Source<T> => {
// Create a new Source from scratch so we have full control over the Source's lifecycle
return make<T>(({ next, complete }) => {
let isCancelled = false;
let resolveSuspense: (value: T) => void;
let synchronousResult: undefined | T;
export const toSuspenseSource = <T>(source: Source<T>): Source<T> => sink => {
const shared = share(source);
let hasResult = false;
let hasSuspended = false;

const { unsubscribe } = pipe(
source,
// The onPush and onEnd forward the underlying results as usual, so that when no
// suspense promise is thrown, the source behaves as it normally would
onPush(next),
onEnd(complete as any),
subscribe(value => {
// When this operation resolved synchronously assign the result to
// synchronousResult which will be picked up below
if (resolveSuspense === undefined) {
synchronousResult = value;
} else if (!isCancelled) {
// Otherwise resolve the thrown promise,
resolveSuspense(value);
// And end and teardown both sources, since suspense will abort the
// underlying rendering component anyway
complete();
unsubscribe();
}
})
);
pipe(
shared,
takeWhile(() => !hasSuspended),
onPush(() => (hasResult = true))
)(sink);

// If we have a synchronous result, push it onto this source, which is synchronous
// otherwise throw a new promise which will resolve later
if (synchronousResult === undefined) {
throw new Promise(resolve => {
resolveSuspense = resolve;
});
}

// Since promises aren't cancellable we have a flag that prevents
// the thrown promise from resolving if this source is cancelled
return () => {
isCancelled = true;
unsubscribe();
};
});
if (!hasResult) {
hasSuspended = true;
sink(0); /* End */
throw pipe(shared, take(1), toPromise);
}
};
16 changes: 8 additions & 8 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4718,10 +4718,10 @@ react-test-renderer@^16.0.0-0, react-test-renderer@^16.12.0:
react-is "^16.8.6"
scheduler "^0.18.0"

react-wonka@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/react-wonka/-/react-wonka-2.0.0.tgz#d62d87c9c93ec3e603ecf1582df3615aadc5c2e9"
integrity sha512-7q0CNBnSltRyzb61joCxKqVntHbRJRhP/WPxEx+zM8l9Yd+0IRevJuPG8iCamgrGphusX5xtEtd4yyX7qvRM1g==
react-wonka@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/react-wonka/-/react-wonka-2.0.1.tgz#75bdf03dbad8ceb8c1066216f635f05ce2b642a5"
integrity sha512-mM2UH2gnK5LLzaqVWd6JCLrB1vO3I4PN/sQZbjvzsjms4vSv+nKwelNUftM0KeC+LtTPC4GGsuxyu2XJnsCUTw==

react@^16.12.0:
version "16.12.0"
Expand Down Expand Up @@ -5981,10 +5981,10 @@ wide-align@^1.1.0:
dependencies:
string-width "^1.0.2 || 2"

wonka@^4.0.5:
version "4.0.5"
resolved "https://registry.yarnpkg.com/wonka/-/wonka-4.0.5.tgz#3384b90ed8c1e6e182d6e2fb18468c33ab94e0af"
integrity sha512-XKnzSpsk2UcPfyjecdc14b7LZSPeOEhYEs+/oAZ+gXV9BuYIcZC3hpapFi2DFHj1Bk38/npusgkiSD0+KdyCzQ==
wonka@^4.0.7:
version "4.0.7"
resolved "https://registry.yarnpkg.com/wonka/-/wonka-4.0.7.tgz#b4934685bd2449367bd72ce7770bfe3e6cc8a68b"
integrity sha512-Uhyl2cgWCUksYtU0Jt8MSzKUqK4BVUrewWxnn1YlKL3Zco4sDcCUDkbgH0i762HJs1rtsq03cfzsCWxJKaDgVg==

word-wrap@~1.2.3:
version "1.2.3"
Expand Down