Page freeze, glitchy animation, and slow scrolling. They follow us everywhere, and we'd like them to stop. This talk will get to the bottom of these issues by delving into the Javascript engine and concepts like task, call-stack, and event-loops. I will also introduce several ways to tackle these problems and walk you through a demo to help you understand.
6. Cause Solution
• "Page freeze.."
• "Responding to
user input too
slow.."
• "Glitchy
animations.."
• ...
Problem
• run-to-completion
• javascript engine
• call stack
• event loop
• task queue
• ...
• worker
• scheduling
• ...
7. This is an exploration
for a better solution,
not a clear solution.
⚠
8. User Experience
Users' feelings about using a product, system or service
https://userexperiencerocks.wordpress.com/2015/08/20/then-my-kiddo-asked-whats-the-difference-between-ux-ui/
9. • "Page freeze.."
• "Responding to user input too slow.."
• "Glitchy animations.."
10. Why do they happen?
Something may be slowing things
down and blocking others. #
12. Run-to-completion
• Each message is processed completely before any other
message is processed.
let running = true
setTimeout(() => {
console.log('Will it be print?')
}, 500)
while(true) {
if (!running) {
break
}
console.log('Running...', Date.now())
}
13. let running = true
setTimeout(() => {
console.log('Will it be print?')
}, 500)
while(true) {
if (!running) {
break
}
console.log('Running...', Date.now())
}
14. 1.set running to true
let running = true
setTimeout(() => {
console.log('Will it be print?')
}, 500)
while(true) {
if (!running) {
break
}
console.log('Running...', Date.now())
}
15. 1.set running to true
2.run setTimeout function
let running = true
setTimeout(() => {
console.log('Will it be print?')
}, 500)
while(true) {
if (!running) {
break
}
console.log('Running...', Date.now())
}
16. 1.set running to true
2.run setTimeout function
3.start while loop block
let running = true
setTimeout(() => {
console.log('Will it be print?')
}, 500)
while(true) {
if (!running) {
break
}
console.log('Running...', Date.now())
}
17. 1.set running to true
2.run setTimeout function
3.start while loop block
4.check if running have changed
let running = true
setTimeout(() => {
console.log('Will it be print?')
}, 500)
while(true) {
if (!running) {
break
}
console.log('Running...', Date.now())
}
18. 1.set running to true
2.run setTimeout function
3.start while loop block
4.check if running have changed
5.print log 'Running...'
let running = true
setTimeout(() => {
console.log('Will it be print?')
}, 500)
while(true) {
if (!running) {
break
}
console.log('Running...', Date.now())
}
19. 1.set running to true
2.run setTimeout function
3.start while loop block
4.check if running have changed
5.print log 'Running...'
6.500ms later... running still true
let running = true
setTimeout(() => {
console.log('Will it be print?')
}, 500)
while(true) {
if (!running) {
break
}
console.log('Running...', Date.now())
}
20. 1.set running to true
2.run setTimeout function
3.start while loop block
4.check if running have changed
5.print log 'Running...'
6.500ms later... running still true
7.running is true as ever..
let running = true
setTimeout(() => {
console.log('Will it be print?')
}, 500)
while(true) {
if (!running) {
break
}
console.log('Running...', Date.now())
}
21. 1.set running to true
2.run setTimeout function
3.start while loop block
4.check if running have changed
5.print log 'Running...'
6.500ms later... running still true
7.running is true as ever..
8.and ever...
let running = true
setTimeout(() => {
console.log('Will it be print?')
}, 500)
while(true) {
if (!running) {
break
}
console.log('Running...', Date.now())
}
22. 1.set running to true
2.run setTimeout function
3.start while loop block
4.check if running have changed
5.print log 'Running...'
6.500ms later... running still true
7.running is true as ever..
8.and ever...
9. ...forever....
let running = true
setTimeout(() => {
console.log('Will it be print?')
}, 500)
while(true) {
if (!running) {
break
}
console.log('Running...', Date.now())
}
56. Task & Microtask
•Task
Task that should execute sequentially in browser
Source : setTimeout, running scripts, UI events..
•Microtask
Async task that should happen after the currently
executing script
Microtask queue has a higher priority than the task
queue.
Source : Promise, MutationObserver, process.nextTick
57. Event loop
1. While there are tasks:
• execute the oldest task.
2. Sleep until a task appears, then go to 1.
58. Event loop - Detail
1. If the microtask queue is not empty,
execute all microtasks
2. While there are tasks:
• execute the oldest task.
3. Sleep until a task appears, then go to 1.
73. • Task is always executed sequentially by event loop.
Other task cannot be performed when any task is running.
• Microtask queue has a higher priority than the task queue.
So, UI-related events cannot be executed again until all
microtasks accumulated in the queue are cleared.
• What if long running stacked tasks or microtasks block event
that is directly connected to UI such as rendering, click, input?
janky UI/UX occurs... ,
76. inputEl.addEventListener('input', (e) => {
const textLength = e.target.value.length
let result = ''
for (let i; i < getSquareCounts(textLength); i++) {
result += makeSquareWithRandomColorHtml()
}
containerEl.innerHTML = result
})
77. inputEl.addEventListener('input', (e) => {
const textLength = e.target.value.length
let result = ''
for (let i; i < getSquareCounts(textLength); i++) {
result += makeSquareWithRandomColorHtml()
}
containerEl.innerHTML = result
})
Too many iterations
78. inputEl.addEventListener('input', (e) => {
const textLength = e.target.value.length
let result = ''
for (let i; i < getSquareCounts(textLength); i++) {
result += makeSquareWithRandomColorHtml()
}
containerEl.innerHTML = result
})
Too many iterations
Costly DOM changes
79.
80.
81. •With another thread...?
- Web Workers
•Split some expensive task into small tasks...?
- Scheduling
82. Web Workers
•Web Workers makes it possible to
run a script operation in a
background thread separate from the
main execution thread of a web
application.
https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API
83. Main Thread Worker Thread
2. result = runLongRunningTask()
1. postMessage()
3. postMessage(result)
4. doSomethingWith(result)
84. self.addEventListener('message', e => {
const result = runLongRunningTask()
postMessage(result)
})
// spawn a worker
const worker = new Worker('worker.js')
// send messages to a worker for request run long-runnging-task
worker.postMessage(message)
// handle a `message` event from a worker
worker.onmessage = e => {
doSomethingWith(e)
}
worker.js
main.js
85.
86.
87. Limitations
• Data is sent between workers and the main
thread via a system of messages.
• Worker cannot access directly the DOM,
context.
97. Recap
• If long-running-tasks or microtasks block rendering, click, and
text input, a janky UI that harms user experience can be
delivered can occur.
• This is due to the structure of the JavaScript engine, event-
loop, etc. and needs to understand and handle properly.
• To handle this, Delegate long-running-tasks to other threads
using Web Worker,
• Or, split the long-running-task properly so that other important
UI events do not block.