Check out a free preview of the full State Machines in JavaScript with XState, v2 course
The "Ways to Invoke an Actor" Lesson is part of the full, State Machines in JavaScript with XState, v2 course featured in this preview video. Here's what you'd learn in this lesson:
David demonstrates implementing an actor into a state machine and how to invoke an actor for a promise, callback, and machine. A brief demonstration of forward(), creating a sequence diagram, and a brief discussion of spawning compared to invoking are also provided in this segment.
Transcript from the "Ways to Invoke an Actor" Lesson
[00:00:00]
>> Okay, so now let's talk about using the actor model within a machine. So I'm gonna make a new machine here, createMachine. And we're not gonna have any states in this machine just because I wanna show you just the basic features of invoking an actor. So with invoke, the machine itself when it's interpreted, it becomes an actor.
[00:00:34]
And so we know this because we've been using state machines in this way where we interpret them, we subscribe to them, we can send stuff to them. And this is a pretty useful interface that covers a lot of use cases. So the other part of the actor model is being able to spawn or create your own actors and so we could do that in next state using invoke.
[00:01:01]
So one of the simplest actors to invoke is a Promise. And so everything that goes in this source by the way for the invoke, is a way of creating that actor. So just like many other functions in XD it takes the context in the event, and it should return the thing that can be turned into an actor.
[00:01:33]
So for instance, we're going to be returning a Promise. And so I'm just going to say, this is gonna be a new Promise that resolves after some time-out. So resolve with 42 and we're gonna have this resolved after a second. Okay, so now let's interpret this machine, And, Let's also subscribe to it.
[00:02:09]
So service.subscribe(state, console.log(state, just to see exactly what happens. All right, so we reload after a second, you see that something happens over here and we get an event pack. We get this done.invoke dot whatever this is. So to make this a little bit nicer, you can also give the invoking ID.
[00:02:38]
So id, we're just gonna be calling this fetchNumber. And so now after one second, this event has a nicer name. It's done.invoke.fetchNumber. And the data that is returned from that Promise or a result from it is in this data property of the events. And so what we could do is have an onDone transition inside of this invoke.
[00:03:07]
So onDone, actions and we are going to, let's just log the event. So I'm gonna say console.log('DONE!') and we're gonna get the events. All right, cool, so we have done and here is the events, we have done.invoke.fetchNumber with data of 42. And so just like the other onDone transition, this can target a different state.
[00:03:40]
So if we have initial loading, states, loading, we can put this invoke inside of that state and onDone, we could go to success, And we could target success over here, so target success. And it's still going to log('DONE, so I wanna log the state.value now. And so now let's see what happens.
[00:04:14]
So we're in the loading state. And after a second we get that same done.invoke.fetchNumber events, but it calls the onDone transition, and it targets the success date. So now, we're in success. One thing that is in SEX mail, which I find to be a really useful feature. And I'm gonna bump this up to five seconds, just so we could demonstrate is the facts that when we leave this loading states, this invocation will actually be cancelled.
[00:04:46]
So whatever this Promise resolves with, it's going to be ignored as we expect because this invocation is only active while the state machine is in the states. So let's now add a transition, so on CANCEL. We're gonna go to the cancelled, so we're just leaving the state, we're not doing any specific cancellation.
[00:05:09]
We're not dealing with this invoke, we just know that it's gonna be cancelled when we leave that states, so window.service = service. I have five seconds to type this, so let me jump this up to 10 seconds. All right, so service.send( 'CANCEL, Wow, I think it's gonna. Okay there we go, we're canceled, somehow I made that below 10 seconds but yeah, we do go to the canceled state and now.
[00:05:43]
The Promise is not, so we don't get that done.invoke dot that data because it was cancelled. So just to verify that this is working. All right, I sense cancel, but it was too fast for me, so I'm gonna do this again, and I'm gonna console.log(' resolved, There we go.
[00:06:09]
So we have resolved, which means the Promise did resolve, but it did not cause any transition. This onDone transition wasn't taken, we don't have this done events. And that's because we're in a state where this transition doesn't do anything. So you get cancellation for free basically. And this is sort of going back to the idea that state machines prevent impossible states and impossible transitions.
[00:06:37]
Because when we're not in the loading state, we should not be handling this onDone of this invoke. You could also have on error if you want, and that would be a good exercise to you at home, just learning how to handle errors in invocations as well. But yeah.
[00:06:56]
So another thing that you can invoke is actually, a lot more flexible than a Promise because a Promise cannot receive any more events. It started and then it's gonna go do something it think, and then it might eventually return with the value. Now, we need something a little bit more powerful than that and so that's what invoking callbacks is for.
[00:07:23]
So invokes callback, I'm just gonna put it up here. It has a special function signature where it takes two arguments. It takes send back arguments where we basically can send an event back to the parents and it takes an on receive. I'm just gonna call this receive arguments as well.
[00:07:46]
And so both of these are really useful for doing communication back and forth between the machine. So I'm gonna say receive and that's gonna take an event and so we could console.log(' Received in that events. And so let's actually just try this out for now. Instead of this, I'm going to be replacing this with that callback.
[00:08:18]
And so we have fetchNumber, and this id is gonna be important for communicating back to that invoked actor. So instead of canceling, I'm gonna have a on notify events, And that's going to, gonna grab send from here, that's going to do a send action. And we're going to send what did we call it, no, let's find out over here actually, yeah, we didn't specify an event, so we could just send any events.
[00:08:55]
So ANY_EVENT, and we're gonna send it to that ID. So fetchNumber. All right, so now when we reload and I'm gonna send the NOTIFY events. It says received ANY_EVENT from the machine. So there's a lot of useful actions like this. This is ANY_EVENT, but if I wants to just straight-up forward that event, I could use the forward action, which I think is in here, might be, let's find out.
[00:09:33]
So forward, and then you could yeah, just specify it that way but that's something that you could discover in the documentation. This is something that we're gonna be using in the actual exercise itself. Okay, so with the callback, you could receive an events and do something based on that event, and you could also send something back.
[00:09:59]
So let's say that whenever I receive an event, I'm gonna setTimeout And I'm gonna send back just type PING, I guess. And we're gonna do that after a second, whenever I receive an event, so let's try that out. So here's how we're gonna model it. When we notify, we're gonna send ANY_EVENT to that, that's number actor.
[00:10:28]
After a second, that actor is going to send a PING event, and we're gonna respond to it. Or we're going to respond to the events in the machine by just transitioning to the success state. So it seems a little bit complicated, but we're gonna make it a little bit more clear in a minute.
[00:10:49]
All right, so let's try this out. We're in the loading state, we send notify, and then after a second, we end up in the success states. Okay, so what exactly is happening over here? I know we just talked about it, but just like using state machines and state charts, it is nice to see them diagram form.
[00:11:11]
And so that's why I like swimlanes.io as a tool for doing this. So okay, we have this machine over here. And this is fetch number, so it's sending ANY_EVENT to fetchNumber. So after one second, fetchNumber is sending the machine a PING events. So this sort of describes the communication between these two different actors.
[00:11:56]
When the machine, sorry, when the I'm just gonna call this the client. When the client sends the machine NOTIFY, that's gonna cause the machine to send ANY_EVENT to fetchNumber, fetchNumber is going to send PING back to the machine. And some of the machine is going to transition to the success state.
[00:12:17]
So this is called a sequence diagram, and it's really useful for diagramming the communication between actors. And so I recommend you use swimlanes.io or other tools for sequence diagrams in your application. Just to describe the different use cases of how different actors can communicate with each other. They're really useful tools.
[00:12:42]
All right, so the last thing that I want to show you that you can invoke, and we're gonna be using a machine instead of a callback here. I'm just gonna call this const fetchMachine, and I'm gonna be creating a new machine over here. So when I enter this machine, let's say that the initial state is fetching.
[00:13:10]
And just to make things simple, after one second, we're gonna go to the success states. And this success state is gonna have type of final. All right, so in this machine, now we have a final state as a top level state. And so this signifies that the machine is done.
[00:13:32]
Now, just like you remember from invoking a Promise, we could think of this invoking of a machine in the same way. Where this machine is like a combination of invoking a promise in a callback because the machine can be done. The machine can also send events to the parents machine and the machine can also receive events from the machine as well.
[00:13:57]
So it is really powerful. And we could also, and this is something that we haven't exactly talked about with final states, but we could also send its data. So we could send it counts 42, which is the example data that I'd love to send all the time. And now instead of this context events callback, we could just provide the fetch machine itself.
[00:14:26]
All right, so now let's see what happens. We're no longer, we don't have the NOTIFY or PING, it's going to invoke the machine, and after one second, this machine is going to be in its final state. And so it's going to send this parent machine, a done event, and we're going to see, hopefully, this done data logged in here.
[00:14:50]
So when we go back here, we see it says done, if we look at the data, we have counts as 42 and we transition to the success date. So that's showing you how machines can also invoke other machines too. And so like I said, we could also send events to that machine and everything's going to work just as expected.
[00:15:15]
One final thing before we get off this topic, with a callback, it is important that in this callback, you might be doing some imperative stuff. Or some leaky stuff such as having a setTimeout or set interval or maybe you open a web socket connection. There's literally anything you could do in this callback.
[00:15:35]
But there needs to be a way to dispose of the callback as well. And so that's why you could return a disposal function. So if I just say let timeout and just being a little bit responsible over here timeout = setTimeout. Then I could I think it's called clearTimeout, I always get confused whether it's clear or cancel, clearTimeouts that's timeout.
[00:16:00]
So this is just being a good citizen preventing memory leaks, just showing you that you can return that cleanup function. All right, so the other thing that is something that I do recommend you research is spawning an actor. And so spawning is basically the same as invoking, but the spawn actor does not live in the state.
[00:16:29]
In fact, it lives for the entire lifetime of the machine. And if you want to stop it, it must be manually stopped. And so that's basically the only difference between spawning an actor and invoking an actor. So I recommend if you want to look up the use cases for spawning in actor, read the documentation on it.
[00:16:48]
There are a few examples on that as well. But we won't be going over that in too much detail in this course. We're just gonna be talking about invoking actors for the sake of this example.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops