Check out a free preview of the full State Machines in JavaScript with XState course:
The "Actor Model Solution" Lesson is part of the full, State Machines in JavaScript with XState course featured in this preview video. Here's what you'd learn in this lesson:

David live codes the solution to the actor model exercise, and answers questions from the audience about queuing messages.

Get Unlimited Access Now

Transcript from the "Actor Model Solution" Lesson

[00:00:00]
>> So let's take a look at the JavaScript file, first. All right, in this file we have a random fetch function. All this function does, is it creates a new promise that randomly fails or succeeds after 2 seconds. Now remember before over here, when we were defining a promise state machine, we said something like on resolve, go to result.

[00:00:32] And we might have something here, where we say target pending actions, start fetching or something like that. Or we might even have that action in the entry of pending. So we could say entry: startFetching. However, recall that this is a fire-and-forget action. It's not expected that this could return anything.

[00:01:01] In fact, if this takes a really long time and we go to a different state, then we might get an event when we're in a completely different state, where that event might cause something unexpected to happen. So in general, when you are invoking something that might call the machine back with an event, you want to invoke it instead.

[00:01:21] So here's what invoking looks like. So invoke is an object that takes in a source, and this is what creates the thing that can be invoked. Just like everything else in ex-date, the arguments, passages, function are the same, it's context and event. We're not using context or events in this particular scenario, so we could just ignore both of them.

[00:01:48] Over here, we're going to return that promise and we're going to say, we're using that promise as a source. So we call when we return randomFetch. So this is going to return with either a message saying the fetch failed, or a response saying that the fetch succeeded. Now there's two other properties in invoke.

[00:02:13] There's onDone and onDone is just your normal transition so we could go to resolve over here. And there's also onError, so when an error happens, we go to target rejected. Now again, this is sugar for what XState is doing internally, there's no new concepts here. Because each of these onDone and onError is the same thing as having a special like, done dots invoke dot and then whatever the name of this invoke is, so whatever, then we go to result.

[00:02:56] So you could just consider both of those exactly the same thing onDone is a transition that accepts an event from this exact source, and it goes to the state and it could additionally perform other actions. In fact over here, let's add an action, which takes the events and logs it, just so we see what we're working with.

[00:03:20] Okay, let's try this out. So here I have a special box it says click to fetch and so when we click, goes pending and after two seconds it goes to result. So let's take a look at the events that we got back. Like I said it's a special XState events we have done invoke machine pending invocation zero.

[00:03:43] This is just a unique identifier for specifying that exact invocation. And so we have this data in the event that the fetch succeeded. So we could use that, of course, we might want to fetch again. So let's go ahead and add transitions and both resolved and rejected to fetch again.

[00:04:03] We could say, onFetch, go back not idle, pending. So now you could see, That it goes to rejected remember, it's random. So eventually we'll get a result but you could see that the behavior for both resolved and rejected is that we go back to pending, which really works.

[00:04:33] Now another interesting thing is that in this pending states, because we don't handle the fetch events, it won't do anything if we try clicking it again. So that's going to avoid a really common problem that I see even in software today where if you click a button that says submit, your users might accidentally click in more than one time, especially if they're frustrated.

[00:04:56] And because of that, you're going to get a lot of unnecessary API requests and even worse, some unintended side effects of that. So if I say console.logFetching, we see that this fetching happens only once. And so we're not handling that at the event handler level, now we're handling that at the state level, and so that's one way that you could prevent bugs like that.

[00:05:34] So one other thing about invoke is that whatever is invoked inside whether it's a promise, a machine, an observable or a callback, those will get cancelled and cleaned up as soon as you leave the state. So that is a special property of invocations is that when you leave pending, this invoke only lasts for as long as it's in its parent state.

[00:06:02] So let's say that we, we have a cancel event so on cancel, let's go to idle. And of course, we need to map this to something, so let's make a new button. Let's call it all cancel and we could say cancel, cancel equals document.querySelector(cancel) and we could create that real quick.

[00:06:28] So cancel and then int our HTML, we could add a cancel button so we have to say button id = "cancel", Cancel. Okay, looks a little bit weird, but that's okay. That's what breaks are for, doesn't quite work here cuz we're using Flexbox. Anyway, so when we click the fetch, we can cancel that and that's not doing anything because I don't think that, that's right.

[00:07:00] So we have an ID, yeah, we have to save, don't we? Okay, cool so, you see, we have canceled that promise. And the way that this works inside is that XState is going to still let the promise happen. But whatever the result of the promises, it's just going to discard it.

[00:07:23] So that's how cancellation works in that scenario. X-rays asks, does it feel like an actor is something similar to a thunk? So thunks aren't exactly the same thing as actors, thunks can be, thunks a thunk in Redux is a fire and forget action. So it's not really related to actors because you cannot communicate directly back with a thunk.

[00:07:49] A thunk might also, well, a thunk can dispatch multiple times, but there's also no concept of cancellation or a life-cycle of a thunk. So Johan asks, an actor can also queue a message, in the actor model in other implementations such as Akka or Erlang, or elixir, Orleans or any of these other actor models.

[00:08:11] Yes, the actors do in fact, cue messages and then they, they play them one at a time. And so that's how they work internally, your actors can work however they want