-
-
Notifications
You must be signed in to change notification settings - Fork 44
Description
Motivation
Web is asynchronous by it's nature and there are a lot of cases when we wait for something, defer/postpone something and so on.
Let's assume we have a user that want to pay using his wallet created in our app. He asks us how to do it and we want to give him an url that either will open wallet creation or wallet details depending on whether he already created the wallet or no.
Both of wallet creation and wallet details are dialogs belonging to the pay page. Existence of the wallet is checked by the request inited by pay page and buttons that open this dialogs are disabled until data is loaded. Everything worked pretty well until we decided to give user a direct url that opens one of these dialogs. The problem is that the data needed to determine what dialog to open is still loading.
Sure we can just rework our UI to have one dialog that shows loading until data is loaded and renders different contents despite these dialogs are not related at all but the essence of the problem is that we have a synchronous event of opening our url and want to handle it asynchronously. In imperative API like effect handler we can do it with ease using async/await but there is no solution for declarative API
Solution
Since almost everything is asynchronous in web, sometimes we want to handle events asynchronously. In imperative API like effect handler we can do it with ease using async/await, but there is no solution for declarative approach. We can rely on effect's done/fail events to call event after an asynchronous action but this works only in case when we have event -> effect -> another event
chain. There is a case when event doesn't trigger effect but depends on data fetched by it. Effect in the other hand is called by another trigger.
In this case we have two branches of code:
- If data ALREADY fetched then we should handle an event
- If data IS NOT fetched YET then we should wait for data and then handle an event
That is where defer
operator comes in.
sample({
clock: defer({ clock: event, until: $itCanBeHandled }),
target: handleEventFx,
})
You can read this as "when event is happened check if $itCanBeHandled is true. If so call the handleEventFx. If no wait until $itCanBeHandled became true and then call handleEventFx.
Let's try to solve the problem described in motivation. We want to open either wallet creation or wallet details dialog depending on whether wallet exists by direct url
split({
clock: defer({ clock: walletUrlOpened, until: equals(walletQuery.status, 'done') }),
source: $wallet,
match: (wallet) => (wallet ? 'hasWallet' : 'noWallet'),
cases: {
hasWallet: walletDialog.open,
noWallet: walletCreationDialog.open,
},
})
In this case we will run the split only when $wallet be ready to determine what dialog should be opened.
Details
If clock will be called more than 1 time only the last event will be deferred and called. Other events will be ignored