Placeholder Image

字幕列表 影片播放

  • Hello.

  • I'm Kang Jaeseok and I'm here to talk about 'Javascript Async for effortless UX'.

  • Sharing this content with all of you here,

  • and at a grand stage like the JS Conf Korea- it's a true honor.

  • There's even small commonalities like this.

  • What I'm going to talk about today is...

  • this is the general flow of my story.

  • First, we are going to talk about a problem.

  • Then, we are going to analyze the cause, and based on the cause, we are going to seek for some solutions.

  • First, I'd like to handle this type of problem.

  • We encounter this problem from time to time when using web applications.

  • The scroll stops, or what I typed doesn't show on the screen right away

  • or the animation isn't smooth.

  • As you can see, these are the main examples that can distract users.

  • There are various causes to these problems.

  • First, the animation itself could've been wrong.

  • There could be a problem in the environment.

  • But in this session, we are going to analyze the cause focused on these factors.

  • One of the features of Javascript language: the run-to-completion,

  • Javascript engine, and the Call stack event loop are the three factors.

  • And based on the analyzed causes, I'm going to suggest some solutions.

  • One thing I want you to keep in mind before we start,

  • is that the things I'm going to explain aren't the exact solutions

  • but are part of the progress in finding a better solution to a problem.

  • Before we start, what is a user experience?

  • I'm not a UX designer or an expert in that field.

  • So I'm being careful here but,

  • when you think about what a user experience is,

  • after all, it's about the user's feeling towards the product, system and service.

  • Then what are the common features of these problems?

  • The interaction that the user intended, like page loading, typing and animation, didn't work in time.

  • If the interaction doesn't work in time

  • Like I said, in the user experience's point of view, it's a negative factor.

  • Then what caused these problems?

  • If the intended action doesn't act in time,

  • we can suspect that something is blocking the act and making it slow.

  • To unravel this suspicion, let's take a look at how the Javascript code works internally.

  • First, I want to mention the traits of Run-to-completion.

  • Run-to-completion is, when a process of one message starts,

  • no other process can get in the way until this message process is finished.

  • We'll look at some example code.

  • What would happen if we run this code?

  • Set running to true first,

  • and the setTimeout function can run.

  • Then, start the while loop

  • and check if the running has changed; but it would still be true.

  • Then the running message would be on the console.

  • After about 500 ms, running still would be true.

  • Then we would never be out of the while loop.

  • You could also encounter this familiar icon.

  • As you all know, this is an icon shown when the Chrome tab process is dead.

  • As a developer, we should understand the run-to-completion process well and work hard not to meet this icon.

  • So, I've shown you the process with example code to explain the run-to-completion process.

  • In the Javascript engine, there's a place that acts as a curser to mark where the code is being processed.

  • That is the call stack.

  • Every time there's a request in Javascript, the requests are kept in call stack in order and are processed.

  • For example, they show what function is called and acted,

  • and they manage the function that should be called next.

  • This is code that calls up the hello function, that has code which writes 'Hello',

  • and also defines the helloJsConf function that has code that writes 'JsConfKorea',

  • and ultimately calls up the helloJsConf function.

  • While this code is processed, let's see

  • what happens in the Call stack.

  • First, the main code block is stacked

  • and by calling the helloJsConf function, it joins the stack.

  • Then it calls the hello function

  • and it would request to write the hello message on the console.

  • By writing the hello message, it's eliminated from the stack.

  • JsConfKorea, which was next to the Hello function call, requests to write 'Js Conf Korea' message and gets eliminated from the stack.

  • So helloJsConf function has done its work, so it's eliminated form the stack.

  • After all the work is done, the main block is eliminated.

  • As you can see, Javascript acts with the Call stack structure and run-to-completion method.

  • When these methods are acted out, what kind of situations would cause a problem?

  • Let's give an example situation

  • If you were processing each request at the call stack, and you come up with a task with some delays, what happens?

  • It operates in the order of requests, like in the exercise before

  • And it encounters a request that takes a lot of processing time, like the function 'someExpensive',

  • then there will be delays in things like displaying 'hello' or 'JSConfKorea', which was performed instantly before.

  • Then we have a question here.

  • Javascript is supposed to process multiple tasks at a single call stack structure,

  • but when we think of using web services

  • we click on something,

  • we scroll, and we type, and the data is added and displayed on screen,

  • and it doesn't look like the tasks are being waited upon and processed in order.

  • Then, how are they managing these complex sync issues?

  • The following could be the answers to this question.

  • Web APIs that help the solution of synchronistic issues,

  • DOM events or XMLHttpRequest; the api's used to call Ajax.

  • There are also setTimeout, which manages the timer, Promise, or RequestAnimationFrame.

  • It's hard to not use these in contemporary web development.

  • And the event loop, which I will discuss further in the next chapter.

  • These two enables asynchronous programming with Javascript.

  • Then we will examine the event loop in depth.

  • MDN describes the event loop with codes, like shown.

  • To interpret: assuming that waitForMessage function operates synchronously

  • the code runs the infinite loop waiting for messages, and if there are any, the code would process the next message.

  • The event loop is not a component of the Javascript engine.

  • Which means, supporting asynchronicity

  • would not be the Javascript engine, but the environment that the engine is driven on, like the browser or node.js.

  • This is where the event loop is grounded.

  • In such an environment, the loop controls which task to shove into the call stack.

  • This is a simple explanation of how the event loop works.

  • If there are tasks, execute the oldest one,

  • And there aren't any tasks, wait for one, and if there's any, go back to number 1 and repeat.

  • Now I will discuss the event loop further with the aforementioned code, using web APIs that manages asynchronicity.

  • Look at the code here: setTimeout is, as you know,

  • a function that creates timer events, wait for a time, and executes them.

  • Like you see in the example code, if there is no factor it will be the default value, 0.

  • The 0 in the timer gives the feeling it should be executed right away, but that's not true.

  • The reason for that, we will find out the steps of how the code works.

  • The Promise is an API dealing with future situations in which asynchronous operations have succeeded or failed at processing.

  • This code restores the Promise's transitioning to empty values with the resolve method,

  • and hands the callback over, when the transition is completed with the then method.

  • Let's take a look.

  • You see, first the setTimeout is called.

  • And the callback goes to the task waiting line.

  • Promise is called over,

  • and the part that was handed over to the then method through callback

  • It should be in the task waiting line

  • but as you can see, its in a slightly different waiting line.

  • In ES6, to deal with the synchronicity, APIs like Promise are added

  • but these deal with tasks slightly differently from regular tasks, which are called microtasks.

  • The difference between tasks and microtasks is this.

  • Tasks are the things that need to be executed in order, in the browser or other places.

  • Their sources can be regular script execution, setTimeout, or callbacks from UI events.

  • Microtasks are asynchronous tasks that come right after the task that's being executed now.

  • Which means that microtasks have higher priorities than regular tasks.

  • Like I mentioned, its sources could be the Promise, Observer API, and the process.nextTick of Node.js.

  • And so coming back to my earlier definition of event loops, we'll need a little modification.

  • If you look at it, before waiting for the task,

  • it needs to check if there are any microtasks

  • and if the microtasks are empty, then it will run the tasks.

  • Then, when we come back to the example code, now we can see what the event loop does.

  • The callback is handed over to the Promise,

  • it is pushed into the call stack through the event loop as a microtask,

  • and it is executed.

  • And the task that displays "hello" would be executed.

  • Then, based on the understanding of event loops,

  • can we solve all the problems if we use web api's dealing with asynchronisity?

  • Unfortunately, no.

  • Still, because of the tasks in front of that, there is still possibility that the next tasks may be blocked.

  • Shall we take a look?

  • Like the example before,

  • call setTimeout,

  • put the callback in the task queue,

  • then call the Promise,

  • The callbacks...

  • One second,

  • The callbacks to execute the expensive tasks of then go into the microtask queue.

  • As I have mentioned, because microtasks have higher priority,

  • the event loop has pushed higher-cost operations into the call stacks first.

  • Therefore, the task of hitting 'Hello'

  • will be blocked by the event loop.

  • Obviously, if the task is completed, then it could be executed.

  • So, though it took a while to get here,

  • to summarize, the causes and process of inconvenient UIs and UXs are as follows.

  • Because tasks are always executed sequentially by the event loop,

  • other tasks cannot be performed until a given task at hand is finished.

  • And since microtask queue has higher priority than other task queues,

  • tasks like UI events cannot be executed until all micro-tasks in queue are cleared.

  • Thus, tasks which take long time to execute,

  • for example, if tasks and microtasks involving multiplication calculation of CPU bounds are being executed,

  • then events directly related to UI, such as click, text, rendering etc, can be blocked.

  • This means that this may be 'an element harmful for user experience'.

  • Then, how can this blocking be resolved?

  • I will illustrate through the demo I've prepared.

  • This material is already public, so please feel free to look it up if you are interested.

  • Prior to the demo,

  • let's take a look at how the demo works, through the code.

  • It's simple code.

  • It's code that whenever a text input event occurs,

  • it creates HTML of a rectangular element of random color, proportional to the number of input characters,

  • and displays it on the screen.

  • There are two main points here.

  • There are many iterations,

  • and that the cost of DOM renewals are high.

  • I think this scenario of CPU bound high-cost operations would be sufficient to recreate the same blocking.

  • Let's take a quick look at the demo.

  • It's a simple UI.

  • There's a text input box in the middle, and the animation surrounds the box.

  • When I type a message, DOM whose number corresponds to the length of the message I typed, would be renewed on the screen.

  • And I'm in the perspective of user interaction this time.

  • I'd like the audience to focus on whether my text inputs are being well-reflected,

  • and if the animation is functioning properly.

  • I will try typing a message here.

  • You can see that the text freezes at about J, and the animation freezes as well.

  • And the rendering is reflected after a few whole seconds.

  • Coming back,

  • this is the result I got from profiling the demo using Chrome developer tool.

  • It looks a bit complex, but there are only two main points to focus on.

  • First, the top frame section.

  • I don't know if you can see here..

  • But, almost three seconds were consumed for just 1 frame.

  • Next, regarding the user interaction in this interaction section below,

  • you can see the analyzed results.

  • The red underlines within the orange blocks refer to the time consumed waiting for the main thread of user interaction.

  • So, the very long red underlines mean that

  • considerable time has been spent for each interaction.

  • So, now I should introduce resolutions for minimizing these problems.

  • I have two main solutions.

  • First,

  • the method of delegating the task to another thread,

  • since the block happens because of the structure of a single call stack and event loop.

  • Meaning that we could try multi-threading, if it's possible on Javascript as well.

  • Or another solution, if the task causing the block is too heavy,

  • then we can split the task into smaller tasks.

  • Multithreading on javascript can be done through web workers.

  • Web workers enable script operation in the background thread, which is separate from the main thread.

  • Worker thread is created off the main thread,

  • so that message-based communication with the worker thread is possible.

  • Through postMessage, execution of long running tasks can be requested to the worker thread.

  • The worker thread will execute this,

  • and there will be no blocks in the main thread due to this execution.

  • When the task is complete, results will be delivered through postMessage again.

  • And the main thread can then do something else with it.

  • Similarly, looking at this from the code,

  • the explanation is pretty much the same.

  • Worker object will be created, and through the postMessage method of the worker object,

  • the long running task can be requested to the worker thread when necessary,

  • and when the worker thread has completed it, the code will handle the results.

  • Then, we will take a look at a demo which utilizes this method.

  • I'll type the same message.

  • Okay, doesn't it seem better than before?

  • But, it still seems a bit slow.

  • Then, let's take a look at the demo profiling results which have been calculated with the explained methods.

  • Let's see.

  • You can see that the duration of frame and the duration of interaction

  • have very much decreased.

  • However, as seen in the demo, this method too fails to provide lag-free usage.

  • Most of the display devices these days are 60fps, I believe?

  • Considering the display at the standard of 1 frame per 16ms,

  • if you look up here, it is taking around 200ms, 600ms and if longer it takes up to around 900 ms.

  • My suggested reason, the operation that is CPU bound has been delegated to the worker thread

  • but operations like DOM renewals are still happening in the main thread.

  • The limitations of web workers are that the main thread and the worker thread can only communicate via a system of messages.

  • Meaning, the worker cannot access the DOM or the context of the main thread directly.

  • Next, there should be a way to run tasks asynchronously by splitting