筆記、What the heck is the event loop anyway? | Philip Roberts | JSConf EU


Posted by s103071049 on 2021-08-01

js 究竟是如何運作? 

js is a single-threaded non-blocking asynchronous concurrent languange. js has a call stack, an event loop, a call back queue, some other apis and stuff.

v8, which is js runtime inside chrome has a call stack and heap

The heap, where memory allocation happens, and then there's the call stack, which is where your stack frames are and all that kind of stuff, but, if you, like, clone the V8 code base and grep for things like setTimeout or DOM or HTTP request, they're not in there, they don't exist in V8.

we have the V8 Runtime but then we have these things called web APIs which are extra things that the browser provides. DOM, AJAX, time out, things like that, we have this mythical event loop and the callback queue

the call stack

one thread == one call stack == one thing at a time

JavaScript is a single threaded programming language, single threaded Runtime, it has a single call stack. And JS can do one thing at a time, that's what a single thread means, the program can run one piece of code at a time.

ex1

function multiply(a, b) {
  return a * b
}

function square(n) {
  return multiply(n, n)
}

function printSquare(n) {
  var squared = square(n)
  console.log(squared)
}

printSquare(4)

the call stack is basically ‑‑ it's a data structure which records basically where in the program we are, if we step into a function, we put something on to the stack, if we return from a function, we pop off the top of the stack that's all the stack can do, ‑‑ so if you run this file, there's kind of a main function, right, like the file itself, so, we push that on to the stack

執行流程

  1. 執行檔案 => put main( ) on the stack
  2. printSquare(4) is a function call => put printSquare(4) on the stack
  3. 進入 printSquare 裡面,square(n) is a function call => put square(n) on the stack
  4. 進入 square 裡面,multiply(n, n) is a function call => put multiply(n, n) on the stack
  5. 進入 multiply 裡面,return a * b => pop out sth off the stack => 將 multiply(n, n) 移除
  6. return multiply(n, n) => pop out sth off the stack => pop multiplier of the stack return to square => 將 square(n) 移除
  7. 回到 printSquare => console.log(squared) => put console.log(squared) on the stack
  8. 雖然 printSquare 之後沒有 return,但我們要結束 function => 所以最後將這些 stack pop out

ex2-blowing the stack

We have a function main which calls foo which calls foo, which calls foo, which calls foo, and ultimately chrome says, you probably didn't mean to call foo 16,000 times recursively, I'll just kill things for you and you can figure out where your bug lies, right.

block

So, the big question then comes is like what happens when things are slow?

there's no strict definition of what is and didn't blocking, really it's just code that's slow.

So console.log isn't slow, doing a while loop from one to ten billion is slow, network requests are slow. Image requests are slow. Things which are slow and on that stack are what are blocking means.

ex1

var foo = $.getSync('//foo.com')
var bar = $.getSync('//bar.com')
var qux = $.getSync('//qux.com')

console.log(goo)
console.log(bar)
console.log(qux)

以此類推

we're doing network request, network is relative to computers, are slow, hopefully that network requests completes, we can move on, wait, move on.

Wait, and, I mean, this network request might never finish, so ... yeah, I guess I'll go home. Finally those three, you know blocking behaviors complete and we can clear the stack, right.

in a programming language is single threaded, that's what happens, right, we make a network request, we have to just wait till it's done, because we have no way of handling that.

why is this a problem ? because of browsers

We've got the synchronous request, the browser can't do anything else. It can't render, it can't run any other code, it's stuck. Not ideal, right if we want people to have nice fluid UIs, we can't block the stack.

asynchronous callbacks

there's almost no blocking functions in the browser, equally in node, they're all made asynchronous, which basically means we run some code, give it a callback, and run that late

console.log('hi')
setTimeout(function() {
  console.log('there')
}, 5000)

console.log('JSConfEU')

stack 執行流程

  1. main( ) 放入
  2. console.log('hi') pop in
  3. console.log('hi') pop out
  4. setTimeout. We know it doesn't run immediately, we know it's going to run in five seconds time, we can't push it on to the stack, somehow it just disappears
  5. console.log('JSConfEU') pop in
  6. console.log('JSConfEU') pop out
  7. wait for 5 secods => console.log('there') pop in
  8. console.log('there') pop out

We log JSConfEU, clear, five seconds later somehow magically "there" appears on the stack. How does that happen? And that's ‑‑ this is basically where the event loop comes in on concurrency.

Concurrency & the Event loop

One thing at a time except not really

JavaScript Runtime can only do one thing at one time. It can't make an AJAX request while you're doing other code. It can't do a setTimeout while you're doing another code. The reason we can do things concurrently is that the browser is more than just the Runtime.

So, remember this diagram, the JavaScript Runtime can do one thing at a time, but the browser gives us these other things, gives us these web APIs, these are effectively threads, you can just make calls to, and those pieces of the browser are aware of this concurrency kicks in. If you're back end person this diagram looks basically identical for node, instead of web APIs we have C++ APIs and the threading is being hidden from you by C++

執行流程:

  1. run file => pop in main( ) at stack
  2. console.log('hi') => pop in log('hi') at stack => console 呈現 hi
  3. pop out log('hi') at stack
  4. call setTimeout => we pass this callback function and a delay to the setTimeout call => pop in (setTimout cb) at stack
  5. setTimeout is an API provided to us by the browser, it doesn't live in the V8 source => (timer cb) at web api => pop off (setTimout cb) at stack
  6. console.log('JSConfEU') => pop in log('JSConfEU') at stack => console 呈現 JSConfEU
  7. we've got this timer in the web API, which five seconds later is going to complete => the web API can't just start modifying your code, it can't chuck stuff onto the stack when it's ready if it did it would appear randomly in the middle of your code so this is where the task queue or callback queue kicks in.
  8. (執行原理:web APIs 執行完後,回傳給 task queue,剩下的交給 event loop 去處理) Any of the web APIs pushes the callback on to the task queue when it's done => The event loop's job is to look at the stack and look at the task queue. If the stack is empty it takes the first thing on the queue and pushes it on to the stack which effectively run it.
  9. the stack is clear, there's a callback on the task queue, the event loop runs, it says, oh, I get to do something, pushes the callback on to the stack. Remember it's the stack is like JavaScript land, back inside V8, the callback appears on the stack, run, console.log “there”, and we're done.
console.log('hi')
setTimeout(function() {
  console.log('there')
}, 0)

console.log('JSConfEU')

setTimeout 0 -- The reason is, generally, if you're trying to defer something until the stack is clear

  • 結果和上面一樣
  • 流程:The setTimeout zero, now it's going to complete immediately and push it on to the queue => since the property of event loop, it has to wait till the stack is clear before it can push the callback on to the stack, so your stack is going to continue to run, console.log “hi”, “JSConfEU” and clear, now the event loop can kick in and call your callback.
  • 小結:That's like an example of setTimeout zero, is deferring that execution of code, for whatever reason to the end of the stack. Or until stack is clear.

同理,for Ajax

console log, “hi”, make an AJAX request, the code for running that AJAX request does not live in JavaScript Runtime but in the browser as a web API, so we spin it up with a callback in the URL, your code can continue to run. Until that XHR request completes, or it may never complete, it's okay, the stack can continue to run, assuming it completes, gets pushed to the queue,picked up by the event loop and it's run.

By the time the callbacks get queued... that fourth callback we asked for a one second delay, and it's still waiting, the callback hasn't run, right. This illustrates the ‑‑ like what time out is actually doing, it's not a guaranteed time to execution, it's a minimum time to execution, just like setTimeout zero doesn't run the code immediately it runs the code next‑ish, sometime, right?

call back

depending on who, speak to and how they phrase things, callbacks can be one of two things

  1. callbacks can be any function that another function calls
  2. callbacks can be more explicitly an asynchronous callback as in one that will get pushed back on the callback queue in the future
// 狀況一
[1, 2, 3, 4].forEach(function(i) {
  console.log(i)
})

// 狀況二
function.asyncForEach(arr, cb) {
  arr.forEach(function() {
    setTimeout(cb, 0)
  })
}

asyncForEach([1, 2, 3, 4], function(i) {
  console.log(i)
})

狀況一:The forEach method on an array, it doesn't run, it takes a function, which you could call a callback, but it's not running it asynchronously, it's running it within the current stack. for the first block of code that runs, it's going to sit and block the stack until it's complete.

狀況二:whereas in the Async version, okay, it's slowed down, but we're basically going to queue a bunch of callbacks and they're going to clear and then we can actually run through and do a console.log

render

it can't actually do a render if there is code on the stack, right. Like the render kind of call is almost like a callback in itself. It has to wait till the stack is clear.

The difference is that the render is given a higher priority than your callback, every 16 milliseconds it's going to queue a rend, wait till the stack is clear before it can actually do that render

So this is ‑‑ this render queue is just simulating a render, every second it's can I do a render? Yes, can I do a render? Yes.

狀況一、非同步

we're doing this slow synchronous loop through the array, our render is blocked, right, if our render is blocked you can't select text on the screen, you can't click things and see the response, right, like the example I showed earlier

狀況二、同步

it's blocked while we queue up the async time out, that relatively quick but we're given ‑‑ we're kind of giving the render a chance between each element because we've queued it up asynchronously to jump in there and do the render.

when people say don't block the event loop, this is exactly what they're talking about. They're saying don't put shitty slow code on the stack because when you do that the browser can't do what it needs to do, create a nice fluid UI.

參考資料:
所以說event loop到底是什麼玩意兒?| Philip Roberts | JSConf EU


#event-loop







Related Posts

[ java ] JDBC 連線

[ java ] JDBC 連線

關於 React 小書:dangerouslySetInnerHTML & style

關於 React 小書:dangerouslySetInnerHTML & style

Web開發學習筆記15 — 呼叫堆疊、同步與非同步、Promise、Async/Await、Conditional ternary operator

Web開發學習筆記15 — 呼叫堆疊、同步與非同步、Promise、Async/Await、Conditional ternary operator


Comments