Performance
The Realtime API has various optimisations to allow you to use it in a simple way, while still getting maximum performance. Our design philosophy is that the "naive solution" should also be best-practice.
When you call getTalkSession
multiple times with the same app ID and user ID, we do not create a new session each time.
Instead, we return the existing active session.
This means that if your code interfaces with TalkJS in multiple places, it is easier and just as efficient to call getTalkSession
each time, rather than trying to pass around the existing TalkSession
object.
This also applies to the session used internally in TalkJS Components.
If you create a session with getTalkSession
, and then mount a UI for that same user with TalkJS Components, the UI will use the same session internally.
This reduces load on the client, allows the data cache to be shared between them, and improves the user experience overall.
For example, if you mount a conversation in a Components UI and also call ConversationRef.subscribeMessages
, the two places will share the same subscription state.
This means that when the user scrolls up to load more messages in the UI, those new messages will also be emitted to your subscription.
Likewise, if you call loadMore
on your subscription, those extra messages will appear in the UI for the user.
To perform a large number of actions at once, you do not need to do anything differently.
There is no getMany
or subscribeMany
method.
Send the actions one at a time, like this:
1for (let i = 0; i < 1000; i++) {2 session.user(`user ${i}`).subscribe(...);3}
The Realtime API is based on websockets, meaning that there is little overhead on each additional call. Unlike HTTP requests, there is no limit to the number of calls you can have in-flight at a time.
Some calls (like UserRef.subscribe
) support "automatic batching" when you send them multiple times in a row.
TalkJS will combines these calls into one batched call which is sent to the backend.
You will get the exact same response as you would if the requests were sent one at a time, except it's faster.
Batching will never change the order that your calls are sent in. This means that if you interleave different kinds of requests:
1session.user("Alice").get();2session.user("Bob").set({ name: "Robert" });3session.user("Bob").get();
Then TalkJS will send these calls one at a time.
If the GET Alice
and GET Bob
requests were combined into a batch, then SET Bob
could not happen in-between the two GET
calls.
In technical terms, when you do a call that supports automatic batching, we wait until the next microtask in the JavaScript event loop before actually sending the call. If you send another call before then, we check to see if the second call can be sent in a batch with the first call. If the two calls are compatible, we combine them into a batch. Otherwise, we send the first call and wait for the next microtask before sending the second call.
There is no subscription.addSnapshotListener
method, where you can attach a new listener to a pre-existing subscription.
So when you want to attach 5 different listeners to a user, you can do:
1for (let i = 0; i < 5; i++) {2 session.currentUser.subscribe(() => console.log(i))3}
This will result in only one subscription on the backend, with your 5 listeners attached to it. We won't officially unsubscribe until all 5 of your subscriptions have unsubscribed.
When you unsubscribe from a subscription, we wait a little bit before officially unsubscribing. This means that if you unsubscribe and then immediately resubscribe, it happens instantly without sending any requests to the backend. For example, if you are subscribed to a user and want to change the callback to something else:
1const subscription = session.currentUser.subscribe(...);23// Later4subscription.unsubscribe();5const subscription2 = session.currentUser.subscribe(...);
This will not trigger any call to the backend, because internally we are still subscribed to currentUser
when you resubscribe.
This is important because staying subscribed is generally much more efficient than subscribing in the first place.
The amount of time the client will stay subscribed is different for each resource.
Simpler resources that change less often (like users) stay subscribed for longer, because being subscribed to something that rarely changes is almost free.
When you fetch a resource that you are subscribed to, we already know the current state without having to ask the backend. This means that the method becomes synchronous and essentially free. For example:
1session.currentUser.subscribe();23// Later4await session.currentUser.get();
This get
call will not trigger any request to the backend and will return the data instantly.
If you know you are going to get
some data repeatedly, consider subscribing to improve performance.
Snapshots are considered immutable, and we try to reuse them as much as possible.
This means that you can skip rendering a UI component if oldSnapshot === newSnapshot
.
For example:
- If a message was edited, we would create a new
MessageSnapshot
but reuse the oldUserSnapshot
for thesender
property. - If 10 messages all reference the same message, they will all use the same
ReferencedMessageSnapshot
. - When a new message is received, we output a new array of messages with the extra message. Pre-existing messages reuse their
MessageSnapshot
.
Specifically, we guarantee that snapshots are immutable. If something changed, we will return a new object. However, we do not guarantee that snapshots are always reused. If nothing changed, we try to reuse the old snapshot. In edge cases such as network loss, we may create new snapshots despite nothing changing.
You should only re-render your UI when oldSnapshot !== newSnapshot
.
However, if you need to be certain that nothing changed, do a deep-equality check.