State Machines in JavaScript with XState, v2

Actor Model in Vanilla JavaScript

State Machines in JavaScript with XState, v2

Check out a free preview of the full State Machines in JavaScript with XState, v2 course

The "Actor Model in Vanilla JavaScript" 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 walks through creating an actor that accepts an event, changes, and holds its own internal state using vanilla JavaScript and how to subscribe to that actor's internal state. The internal state of an Actor can include other actors that it decides to create.


Transcript from the "Actor Model in Vanilla JavaScript" Lesson

>> Let's actually create an actor and we're gonna be starting from scratch here not in this file but in main.js. So, I'm gonna to be deleting all of this, all right. So let's create a very simple actor. And echoing the the first exercise that we did, we're gonna be doing this without using any libraries.

So we're gonna push XD to the side a bit just so that we understand the actor model a little bit better. So we're gonna have function createActor and let's give it a behavior as well. So this behavior can be represented by a function just like the reducer function that we talked about in the beginning.

So I'm just gonna call this function countBehavior and that takes the current states in an event and let's just make this really simple. So if event.type = INC, so if we're incrementing then we return state where the count is state.count + 1, all pretty simple stuff, return the state.

So, in createActor what we ultimately want to do is we want to return an object where, just like we described in this diagram over here, we could send an event to the actor and that should do something. Now, this send is a void function, which means it doesn't return anything.

It just receives the event and then it could change its own behavior with the event but it's not gonna be synchronous or immediately respond to the sender. And so let's also have an initial state over here. So we're gonna call it currentState = behavior. Let's also have an initial state here.

So we're going to just have the current state be this initial state. So whenever we're sent an event, we're going to update the current state. So currentState = behavior(currentState and the event that we received. Now this of course looks very familiar to the first exercise we've done. So, in a way we're coming full circle over here.

So now let's create this actor so const actor = createActor, and we're gonna create it with our countsBehavior, which, again, is just a reducer, so countBehavior. And we're gonna give it an initial state of count of just 42, just to make sure that things are working and = actor.

Okay, so now we have our actor and we see that this actor, it definitely has its own local state. And we know this cuz, well, we don't know this, because it's internal, it's local, we can't see it, but we can send it events. So, just to make sure that things are happening and get a little bit of observability in here, I'm gonna console.log the current state.

And now we could see that if we send the actor an event, so type INC, It's going to log 43. Remember, we started on 42 and so now it's on 43. Now we're gonna send it INC again, 44, 45, 46 and it keeps going up and up. So that's the actor changing its own internal states.

Now its internal state can include other actors that it decides to create. So all right, we have send. But to make things a little bit more practical for real world usage we also want some way to subscribe to the actor's current state, if the actor so chooses to share that state with us.

So, let's create, I'm just gonna call this listeners, and this is going to be a set. And so we're gonna subscribe to these. So subscrib Listener and listeners.add that listener and we're also gonna call the listener with the current state. So every time the state changes here, now we're gonna have some way of notifying or emitting the current state to all of our listeners.

So listeners.forEach listener, we're gonna call listener with the new currentState. Okay, so let's try this. If we do actor.subscribe now, and we say whatever the value is, we're gonna console.Log the value. And now if we send, it's going to log the value all the same. Remember, we got rid of our console.log in here and now we're doing it by using that subscriber.

So you could think of this subscriber as an implicit way of sending events. And so what we could sort of conceptually do with actors too is have some way of saying, okay, let's pretend that I'm already subscribed to you but I'm not going to be listening. Or I don't want to receive an event for every single time you update.

I just sorta want you to cache that away. And so I can easily access that value that you emitted to me, just whenever I want. And so that's what we could do with, if we have some sorta getSnapshot function, we could just return the currentState. All right, so now if we send the actor a bunch of stuff and we say actor.getSnapshot now we have counts 51.

Because remember, this is in implicit subscription, we're not directly reading the actor's internal state. We're just getting the last known value or the last known snapshot of that actor. And so, this in short, the reason why I created this from scratch is becasue I wanted to show you just how things are working internally with X State and X State's version of the actor model.

So the core the functionality of an actor is being able to send it an event. These other two things don't directly break the actor model, but they just make it a lot more practical for real world usage. So subscribe and getSnapshot, you can think of these as abstractions on top of the actor model.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now