Hello everyone! How's it going? Let me see if I can get
my slide clicker out. Yeah, this is my first
time in Singapore. Yeah, it's nice. This is already going well. If I get an applause for that.
That's fantastic. I haven't really
looked around yet, like I landed just before the conference, so I can't say I've really
experienced it yet, but I'm going to stay on for a few days
afterwards, and have a proper explore. I brought my partner
along with me as well and I have to say that was
a big mistake, a huge mistake, because she's been really
looking forward to this trip, and especially over the last couple
of weeks she's been saying things like, two weeks till we go to Singapore, one week till we go to Singapore, five days to go, four days to go, and I am like, could
you please not do that because what I'm hearing is five days, you only have five days
to finish your talk. Four days, three days,
you're still not ready, are you? She's like the worst project manager ever just
taunting me on how unprepared I am, but then – she's brilliant because she knows how
to put up with me because I stress. I stress pretty easily. I stress about things that
aren't really problems. I stress about things that have
really nothing to do with me. Someone posted this picture
on Twitter last week, He's holding a MacBook between
two fingers above concrete! I have not slept since I saw this picture. I stress about code like this, firstly
because there's no semicolons. Give me a cheer if you use
semicolons in JavaScript. Yes, good to hear. The rest of you are monsters. But that's not the main thing
that stresses me out. It's because it's adding stuff to the DOM and then hiding it like presumably
to show it some time later, like on click or something and that stresses me out
because it's like, can we be sure that the user's not going to see a flash
of that element before it's hidden and I've never been able
to re-create this problem, I've never seen it happen, but you never know when it
comes to race conditions. So, I’d always just swap those lines
around, just so I can get some sleep. But really, there's no race
condition here because the timing of
running code and rendering is all tightly defined
and mostly deterministic. And that is thanks to the event loop, and if I do a half decent job
in the next 30 or so minutes, you'll know why things run
in the order they do and I don't know,
it might even make sense, but there's no promises there. So, web pages have a thing that
we tend to call the main thread. Here's the main thread. We call it the main thread
because loads of stuff happen here. It's where JavaScript happens. It's where rendering happens. It's where the DOM lives. And this means that the bulk of your stuff
on the web has a deterministic order. We don't get multiple bits of code
running at the same time, like trying to edit the same DOM and giving you a world of
horrible race conditions. But it does mean that if something
on the main thread takes a long time and by a long time I mean
like 200 milliseconds, that's a long time in terms
of user interaction, then it becomes really noticeable because it's blocked loads
of other things, like it blocks rendering,
it blocks interaction and I think it's difficult
for us to think in this way because as humans we are
extremely multi-threaded, like I can stand here,
I can wave one hand, I can stand on one leg,
I can wave the other leg and all the time I'm speaking,
I'm breathing and processing audio
and visual information. As humans we don't really
have a main thread. We don't really have things
that block unrelated things. I mean we have one
and that is when we sneeze because as you begin to
sneeze just stuff shuts down. You know? The first thing you lose
is the ability to talk, then you pull a stupid face. If you're driving at this point,
this is where you think “huh, I hope no one dies,
least of all me.” And then the human body becomes entirely
single-threaded like you were sneezing. Nothing else. You can't see, hear, think. You can move and make noises but only
in ways the sneeze wants you to, you have no control over this at all. And then it's over, right? You essentially wake up and you
find out if your car is still on the road, if you have the same number
of limbs you started with, the same number of passengers
you started with. Needless to say, we don't want to
write code that is like a sneeze. So, although we have
this main thread thing, we tend to spawn
a whole series of threads like for networking stuff, encoding and decoding, crypto,
monitoring input devices, but once these threads have done something
that the page needs to hear about, they need to sort of come back to the
main thread to give it that information and it's the event loop
that orchestrates all of this. Take setTimeout, for instance. Is it badly named?
Yes. Are the arguments in the wrong order? I'd say so. But, have you thought
about how it actually works? Well, let's write a web standard for it because that's what I do these days. We'll start with... The setTimeout method, when invoked,
must run the following steps: Wait ms milliseconds, invoke callback. Done. But this isn't quite working because spec text like this, this runs on the same thread
as whatever invokes it, and in this case,
it's invoked by JavaScript, so this is running on the main thread. So, if we say wait
5000 milliseconds, we are waiting 5000 milliseconds
on the main thread. We're blocking lots of other stuff, so this spec is very sneezy right now, so we need to change that and we do this. We run the steps in parallel which is magic spec speak
for get off the main thread or get off this thread and run this stuff kind of at
the same time as other stuff. But we create a new problem here because now we're invoking a callback
from something other than the main thread and I mean, there's no way
this can really work. You would end up with lots of
JavaScript running in parallel, still editing the same DOM and you'd end up with all
of these race conditions. So, what we do is this,
we queue a task and we queue a task to get back on
to the main thread at some point and now we're calling JavaScript on
the thread where JavaScript lives, so it all works. And this is a core part of
how the browser works. If you click the mouse, like how does that get from the
operating system into your JavaScript? It queues a task. When you fetch something how did
you get the response into your JavaScript? Well, it queues a task. And you send a message
from a page to a worker, once again it queues a task to do that. So, the first part of the event loop
I want to look at are task queues, and this is the oldest part
of the event loop. Rather than look at the spec I thought it might be easier to
try and visualize the event loop. So, here it is. This is it. I hope that clears up
any questions you have. Actually, I really hope it does because I based the whole talk
around this one diagram, so I hope it works. But yeah, without anything to do, the event loop just spins round
and round in a CPU efficient manner. Now, this visualization is running
at a fraction of a percent of real time, and it's still kind of too fast
to really see what's going on, so let's slow things down a bit. When we queue a task
the event loop takes a detour, so this here this detour here,
this is where tasks happen. So, at some point the browser
says to the event loop, hey, I've got a job for you to do and the event loop is like, excellent, okay, add it to my to-do list and I'll
get round to it at some point. No problem. Done. What if we do this
like using setTimeout? We queue two callbacks that we want
to run after 1 second, 1000 milliseconds. Well, according to the spec we wrote
these two algorithms go parallel, each waits for a thousand milliseconds and then they need to come
back on to the main thread, and they do that by queuing a task. So, the browser says to the event loop, hey, I've got something here that
wants to do main thread work. In fact, I have two things and it adds each one as a separate
to-do item in the task queue. The event loop's like,
sure, that's fine, I'll get around to it. So, it runs the first callback, it goes around the event loop and runs the second callback. And that's tasks. And it would be pretty
simple if that's all it was, but it gets more complicated when
we think about the render steps, and this is what the browser uses to
update what's actually on the screen. The render steps are another detour and that involves style calculation. This is looking at all of
the CSS that's going on and working out what
applies to each element. Layout, this is creating a render tree, figuring out where
everything is on the page and where it's positioned. And then creating the actual pixel data,
doing the actual painting. So, at some point the browser
will say to the event loop, hey, you know, we need to
update what's on the screen and the event loop's like no problem, I'll get around to that next time
I go around the event loop. Now, I don't know about you, but I would consider myself
an expert at coding badly, but I can take very
simple bits of JavaScript and create infinite loops out
of them in places I least expect, but let's take a closer look at
what happens when I do that. Here's a page with a gif and some text and a big button that runs
an infinite JavaScript loop. So, if I click that button,
everything stops. The gif has stopped. I can no longer select text. The whole tab has kind
of come to a standstill. Code for this is simple. This is just button click while (true). So, how do we actually visualize this? Well, the user clicks the button. So, the browser says, hey, event loop,
I've got a task for you, and event loop's like
yep, no problem, I'm on it. But this task never ends. It's running JavaScript forever. A couple of milliseconds
later the browser says, hey, event loop, like we need to update
that gif that was on the page. So, if you could just render at your
next earliest convenience that would be fantastic. The event loop's like, yeah, okay, I'll get around to that right
after I finish this infinite loop that I'm busy doing right now. Then the user tries to highlight text and that involves like hit testing, involves looking at the DOM
to see what the text actually is. So, the browser says, hey, I've got a couple of more
items for your to-do list there. And the event loop's like,
are you having a laugh? Like do you know how long it takes
to perform an infinite loop? It's a long time, you know. There is a clue in the name. So, that is why a while loop blocks
rendering and other page interaction, but this is a good thing in practice. We look again at
the code that I started with. I used to worry that this would
result in a flash of content, but it can't, right, because this script
runs as part of a task and that must run to completion before the browser can get
back around to the render steps. The event loop guarantees your task will
complete before rendering next happens. It still stresses me out though, I'll always swap these lines
around when I see it in code. So, a while loop blocks rendering, but what about this? So, this is a loop, but each time we go round the loop we're using setTimeout
to queue the next call. Well, let's find out. So, very similar testing like before. I click the button and things are still working. In fact, it kind of looks
like nothing has changed, but in the background
here's what's happening. We queue a task, go around the event loop, pick up that task and we queue another
task as a result and that just keeps happening
and happening until the end of time, but as we've already seen like only one
task can be processed at a time. So, when it's processing a task, it's having to go all the way around
the event loop to pick up the next task. So, that means at some
point the browser can say huh, we should update
the display for that gif and it can and it can go around
and update the display, and that's why a setTimeout
loop is not render blocking. But if you want to run code that has
anything to do with rendering a task is really
the wrong place to do it because a task is on the opposite side
of the world to all of the rendering stuff as far as the event loop is concerned. What we want to do is we want to
run code in the render steps. I want to run code here. And the browser lets us do that and it lets us do that using
requestAnimationFrame. Another badly named function, I think, but it's really good for this purpose. RAF callbacks, they happen as part
of the render steps and to show why this is useful, I'm going to animate a box,
just a box using this code. So, I'm going to move
that box forward one pixel and then use requestAnimationFrame
to create a loop around this and that's it. That's all it does. So, that's requestAnimationFrame, but what if we switched
requestAnimationFrame for setTimeout? It looks like this. Now, this box is moving faster. It's moving about 3.5 times faster, and that means this callback
is being called more often, and that is not a good thing. That's not a good thing at all. We saw earlier that rendering
can happen in between tasks. Yes, but just because it can
happen doesn't mean it must. In reality we can take a task,
should we render? No, it can't be bothered yet. Go around the event loop,
pick up another task. Shall we render now? No, it doesn't feel
like the right time. Many tasks can happen and before the browser goes, yeah, actually next time
we will update the display and the browser gets
to decide when to do this and it tries to be
as efficient as possible. The render steps only happen if there's
something actually worth updating. If nothing's changed, it won't bother. Like if the browser tab
is in the background, if it isn't visible, it will
never run the render steps because there's no point, but also the majority of screens
update at a set frequency. In most cases that's 60 times a second. Some screens go faster,
some screens go slower, but 60 Hertz is the most common. So, if we changed page styles
like a thousand times a second, it's not going to run the render
steps a thousand times a second, it will synchronize
itself with the display and only render up to a frequency
the display is capable of. Usually, 60 times a second. Otherwise, it would be a waste of time, like there's no point rendering
stuff the user will never see, but that's what
setTimeout is doing here. It's moving faster because it's updating the position of
that box more times than the user can see, more times than this display
is capable of showing us. Also, so far, we've been using setTimeout as this
kind of shorthand for "queue a task" and it isn't really because even though we've put
0 milliseconds for the callback it's more like 4.7 is what the
browser will use as a default. The spec says the browser
can pick any number to use, but in the things I've tested
it seems to be about 4.7. There isn't a single method
that just queues a task, but we can kind of fake it
using message channels. So, I ran a test with that and if you're sensitive
to flashing images, it might be best to look away now because that looks like this. There're so many tasks happening that it kind of just looks like the
box is getting a random position. We're getting a task every
two-hundredths of a millisecond. So, rendering can
happen between tasks, but you can have many, even tens of thousands of
tasks between renderings. Okay, flashing image has gone now. Let's imagine each of these is a frame
that is displayed to the user. So, our rendering steps they happen
at the start of each frame and that includes like
style calculation, layout and paint, not necessarily all three every time. Depends what actually needs updating, but I like this. I like this.
This is very neat and tidy. This is a beautiful picture. Tasks on the other hand,
they couldn't give a stuff. They just kind of appear
anywhere they fancy. The event loop ensures that
they happen in the right order, they happen in the order
they were queued, but in terms of
timing within a frame there is no kind of
ordering here at all. And we saw this with our setTimeout. We were getting four per frame,
three or four per frame and that means that
three-quarters of those callbacks were wasted effort in terms of rendering. Old animation libraries used
to do something like this, where they were trying to
use a millisecond value that's going to give them
roughly 60 callbacks per second and they're assuming
a lot about the screen there. They're assuming a screen is 60 Hertz, but that was the common case. So, it kind of worked,
it eliminated some of the duplicate effort. Unfortunately, it was a massive hack because setTimeout was not
designed for animation and it really shows like due to inaccuracies
you can end up with drift. So, what's happened here is we're
doing nothing in one frame, and then in the next frame we're
doing twice the amount of work, and that is a visual jank to user,
it doesn't look great. Also, if one of your tasks runs long, you can end up moving
the render steps around because it's all running
on the same thread and you're sort of disturbing
that lovely routine that they have. If we use requestAnimationFrame
rather than setTimeout, it would look a lot more like this. All neat and tidy. All nice and ordered. Everything is within
the timing of the frame, even this longer task here. When I see performance traces
like this, this makes me happy. This is showing a good user experience. Makes me feel very calm. You can't avoid tasks
completely of course because things like click events they're going to be
delivered to you in a task, and generally you want to respond to
those as soon as possible, fair enough, but if you have things like timers or you have stuff coming
from the network, I really recommend using
requestAnimationFrame to batch that work together, especially if you already
have animations running because you can save yourself
a lot of duplicate work. I treat tasks like I treat people
who drink fizzy water, like I acknowledge that they exist but I keep our interaction
to a minimum because I do not trust them at all. I mean I would consider myself
an empathetic person, but I have limits, like I think soda water
is totally disgusting, and I cannot think of a way that a human being could drink
fizzy water without gagging or being sick or passing out
or something. So, when I say someone
who does drink fizzy water, I think there must be something
else going on with them. I maintain a Twitter list of people
who drink fizzy water. It's not a creepy thing. I just want to make sure
I know what they're up to and what they're doing, but when I find the link
between these people and Brexit and Donald Trump, I am blowing the case wide open, taking him straight to the FBI and it pains me to
tell you this, JSConf, but this conspiracy goes
straight to the top. I saw him in airport
drinking fizzy water. Every now and then someone
will say to me, “oh, but Jake, you drink Diet Coke, and I think you'll find the main
ingredient is fizzy water, ha ha ha.” No, that's different, that's completely different like the main
ingredient of air is nitrogen, right? But you would still die
if that's all you breathed. So, it's more like that. You cannot survive on just fizzy water. What was I talking about? requestAnimationFrame. Right. There's one more detail
I want to get to and this is something that catches
a lot of developers out, it caught me out. requestAnimationFrame it comes before
processing CSS and before painting. So, code like this
might seem expensive, like we're showing and hiding
a box many many times, but this is actually really cheap, like JavaScript will always run to
completion before rendering happens. So, while you're doing this the browser just sits back, and it lets you have your
fun changing a value and it doesn't really think about
it in terms of CSS at all. And then at the end when it actually
comes around to the render steps it goes, right, what did you
actually change in the end? And the only bit that
matters is this final line. And this explains a gotcha in CSS or at least something
that caught me out. I had a thing, right? that I wanted to animate from
an X position of 1000 to 500. And that sounds easy, right? So, I had my listener here. I set the X position to 1000, I told it to transition and I changed the value to 500, but that animated from 0 to 500 and I was like come on, browser,
that isn’t what I asked you to do. It's very clear. I said 1000 transition to 500. What? I figured out that maybe I'm giving it
too much information all at once. And it's the same
reason we saw before like the browser is not
going to think about it because I am just to install
one block of JavaScript. So, it's going to ignore
that first transform value. So, I go, okay, fair enough, what I'll do then is
I'm going to put this, this second bit, I'm going to put
inside a requestAnimationFrame and now it still animates from 0 to 500, and I was like what is going on here? Well, I will tell you what's going on
because I finally figured it out. The user clicks on the button
and that's a task. So, we come around to here and this is where we set the
initial transform and the transition. Fine. We queue an animation frame, and we go around and this is where we set the destination, the final transform value. But the browser doesn't think
about CSS until this next step over, the purple block there. This is where it calculates the CSS. So, again it totally
misses the first value because it hasn't thought about styles
in between those two things being set. And that's why to make this work you need to use not one
but two requestAnimationFrames and now this will animate
from 1000 to 500. Incidentally, there is a hacky
alternative to this. You can use something
like getComputedStyle and just access one of
the properties on it and this forces the browser to perform style calculation a lot
earlier than it naturally would, but it makes the browser take note of all
of the things you set up until that point. So, it's like oh, okay,
transform = translateX(1000) that's a thing that this element does. But you need to be
careful with doing this because you can end up
forcing the browser to do a lot of extra style work
than it really wants to, it only really wants to do
that once per frame. In reality the best way to deal with
this would be the animation API, the web animation API, because you can just say I want it
to go from here to this other value and it all just works, but that's only in Chrome. So, it's not really worth
talking about right now. So, if the position of requestAnimationFrame
within the render steps, if that was a surprise to you, if that was something
you didn't know already, it's probably not your fault. You might have been misled
by particular implementations because Edge and Safari,
they get this very wrong, they put RAF around about here, most notably they put it after paint. And that's kind of annoying because it means like if the user clicks
somewhere or something happens, and you want to batch that work you've
been using requestAnimationFrame, Edge and Safari they will render
before they get to your callback. So, the user is going to see something and that means that you're not going
to see the actual changes you make until the next frame along and that's adding quite a significant
delay to things appearing on the screen and it also makes it really difficult
to batch work together. I hope this is something they fix soon. There's been activity
on the bugs recently, but the web standards
say it should be here, and that's where it is in
Firefox and in Chrome. Okay, that's enough about
requestAnimationFrame. I want to take a look at microtasks. This is probably the least understood
part of the event loop I'd say. I strongly associate
microtasks with promises, but this is not where they started. Back in the 1990s, browsers wanted to give developers
a way to monitor DOM changes and the W3C went, okay,
we'll sort that out for you, and they gave us mutation events. So, this is where I could say, okay, I want to know when a node
is inserted into the body element. And fine, excellent, and you get a series
of other events as well. But, in practice this
was pretty problematic. We take this bit of code here, what I'm doing is I'm adding a
hundred spans into the body element. How many events would you expect
to receive as a result of this? One event? One event for the whole operation? Nope. 100 events? One for each span. Yes. But also, another hundred
for this line here when the content is going
into the actual span, a text node is going into the span and because these events bubble, this simple piece of code is going
to land you with 200 events. And because of this like relatively
simple DOM modifications ended up triggering
thousands of events and if you were doing like a tiny
bit of work in these listeners that quickly became a big bit of work, and it was a performance disaster. What we really wanted was a way to
sort of hear about a batch of this work. It's similar to what we
saw with styles before. We want the browser
to kind of sit back, let us do some stuff and then at a convenient point, say,
some stuff changed. Here is a kind of an event or something
to represent all of those changes. We want to hear about
it once not 200 times and the answer became mutation observers, and they created a new
queue called microtasks. A lot of documentation
I read about microtasks suggests that it happens like,
I don't know, every turn of the event loop or it happens after a task
or something like that and it is kind of true. There is a single place on the event
loop where microtasks happen, but that is not where you'll
generally encounter it. They also happen whenever
JavaScript finishes executing. That means that the JavaScript stack
has gone from having stuff in it to having no stuff in it and that's where we run microtasks. So, you can end up with microtasks
happening halfway through a task, you can have microtasks in the render
steps as part of requestAnimationFrames, kind of anywhere,
anywhere JavaScript can run. So, that means this JavaScript
will run to completion adding a hundred spans
and their contents. JavaScript finishes executing and we get our mutation
observer callback. Promises made use of them as well. So, here we queue a microtask
and then log(‘Yo!’), JavaScript has finished executing. So, we go for the microtasks,
and we log(‘Hey!’). And that means when the
Promise callback is executing you were guaranteed that no other
JavaScript is midway through at the time, the Promise callback is right
at the bottom of the stack, and that's why promises use microtasks. But what happens if we create
a loop using microtasks? A bit like we did with setTimeout before. Same demo again. Click the button and it blocks rendering, it blocks the tab in the same
way a plain while loop did, very different from setTimeout before. So, Promise callbacks are async, fine, but what does async actually mean? I mean all it means is that they happen
after synchronously executing code, so that's why we get (‘Yo!’)
before (‘Hey!’). But just being async doesn't
mean it must yield to rendering, doesn't mean it must yield to any
particular part of the event loop. We've looked at three
different queues so far. We’ve looked at task queues, animation callback queues which is where requestAnimationFrame
callbacks happen and now we're looking at microtasks, and just to make your
lives a little bit easier they all are processed
very subtly differently. Like we've seen with task queues, we take one item
and we take one item only and if another item is queued
it just goes to the end of the queue. Fine. Animation callbacks they
happen until completion, except ones that were queued while
we were processing animation callbacks. They are deferred to the next frame. Microtasks on the other hand they
are processed to completion including any additionally queued items. So, if you were adding items to the queue
as quickly as you're processing them you are processing microtasks forever. The event loop cannot continue until
that queue has completely emptied and that is why it blocks rendering. I get really excited about this stuff. I hope other people are
excited about this as I am. Thank you. One person. Excellent. You know what, I used to have
a real job like many of you did. This stuff like it's sort of speaking
the standards work, the creating slides. This used to be my hobby but then my hobby became my job, and now I have no hobbies. I'm a boring person now, and I didn't actually notice when
this happened, like genuinely. The first time I noticed is
when I went for an eye test and the optician just
making small talk asked, “and what are your hobbies?” I am like, “oh shit, when did this happen?
I don't have any.” Like I said, I stress, so I got a bit like oh, I can't
say nothing, who says nothing? So, it's just totally true. I panicked and I said
“I play the piano.” I don't play the piano. And then I stressed even more because
I thought like she was going to say, “Oh well. That's great. We don't need to use
the letters chart then, here's some sheet music. Can you read me the first five bars?
What chord is this?” Thankfully she didn't. But yeah, my optician
now thinks I'm a pianist. That’s great. I don't go back to the opticians anymore.
I'm terrified of like more piano chat. I don't know how I survive
in the real world. I think you're ready for this next one. Occasionally I run
little JavaScript quizzes. Maybe that's my hobby. Nope. That's work too. Never mind. Yes, I run little JavaScript quizzes,
and this is my favorite question. So, I've got a button
and on click I resolved a Promise and then log something. But I have two event listeners on
the same element doing the same thing. So, if the user clicks a button,
what happens? In which order are things logged? Well, our first listener executes.
Great. So, that's on the JavaScript stack. Queues a microtask and then we get to the next
line where we log Listener 1 and that's the first answer. Most folks agree on that bit,
but what next? And I ran a Twitter poll
on this last week. I've been speaking to a few of you,
a few of you saw it. Most people would say the next
thing logged is Listener 2, that's 63%, 5% of people think NaN
is just logs and then Infinity, that is not the answer, but fair enough to that 5% of people, but yes, we've got 63% and Listener 2 is the wrong answer. This is a real gotcha with script, but if you thought it was Listener 2
then you're in very good company. So, don't worry about it. Our Listener has finished. So, we've gone from having something
on the JavaScript stack to nothing. So, it is Microtask time. We are going to run that promise and we are going to log Microtask 1 and there we go. And then it's time for
the second listener and that works the same. So, the order is Listener 1 Microtask 1, Listener 2 Microtask 2. But that's if the user clicks the button. What if the button is
clicked using JavaScript? Oh, yes, it's different. For starters, our script is on the stack. We call click and that synchronously
dispatches the events. So, we start with Listener 1. Great. We queue a microtask and we log Listener 1 and now it's microtask time. No. No, it is not microtask time. We can't run microtasks. This is where it's different because our JavaScript
stack is not empty, button.click has not returned yet. So, we move on to Listener 2. We queue another microtask and now we log Listener 2 and this is where it diverges. And now, our listener is done. In fact, all our listeners are done. button.click returns, our JavaScript stack empties and now we can process
those microtasks and they happen in order and this has real-world implications. So, beware if you're using
promises in this way, if you're using automated tests as well because automated tests if they're
clicking things on the page, they're likely to be using
JavaScript to do it and that can change
the behavior of your code. This also came up when
we were looking at how to add observables to the DOM and how they'd integrate
with promises. We hit this question, If we have a promise that represents
the next click of a particular link, it's just this little bit of code here, can someone use that promise
and still call event.preventDefault? Promises are async. So, have we missed our chance
to prevent the default here? Turns out no, it's fine.
It's totally fine. This works. This just works unless again
the user clicks the link or some code clicks
the link using JavaScript. And this is the final puzzle of the talk.
I'm overrunning a little bit. To figure this out we actually
need to take a look at the spec. So, this is a very rough description of
how the spec for clicking a link works, but we start by creating an event object and then for every listener we have we invoke that listener
with the event object and then we take a look at, has that eventObject's
canceled flag been set. If it's been set then we're not
going to follow the hyperlink, but if it's unset,
we will follow the hyperlink. When you call event.preventDefault it sets this canceled flag
on the event object. So, if the user clicks
a link that's fine, our microtasks happen here
after each callback because that's where
the JavaScript stack empties, but when we call
click with JavaScript, it's just going to call out to
Process a link click algorithm, and it only returns once
that algorithm is complete. So, the JavaScript stack never
empties during this algorithm. So, our micro tasks can't happen. So, it hits this step 3
where it looks at the eventObject and even if you've got loads of promises
trying to call like preventDefault it's too late. It's going to follow the hyperlink and then sometime later those
promise callbacks happen, but you've missed the boat. You've missed the point where you
can actually cancel that event. So, remember that microtasks
they behave quite differently depending on the JavaScript stack. Okay. So, that 34 minutes, that was a massive brain dump of everything I know
about the event loop and the various steps
and various queues. I have found that
knowing this stuff like it prevents that case where you kind of got a bit of code
that's not doing what you want. So, you just wrap
it in a setTimeout and now it kind of works sometimes. Knowing the stuff kind
of helps avoid that. It helps me avoid jank by getting stuff running on the correct
part of the event loop as well, and I hope I’ve managed to explain this
stuff in a way that is helpful to you too. Like I said I'm a stressful person. So, as a result of this I'm now
going to go and collapse in a pile, but you've been a great audience. Thank you very much. Cheers!
This talk has one of the best simplifying illustrations I have ever seen.
Even a dumbass noob like me understands event loop. So nice.
Microtasks, do they exhaust straight after each "yellow box"?