JavaScript: The New Hard Parts
Introduction
The 5 capacities we look for in candidates
- Analytical problem solving with code
用代码解决问题的能力
- Technical communication (can I implement your approach just from your explanation)
技术沟通能力(我能仅仅通过你的解释就能实现你的方法吗)
- Engineering best practices and approach (Debugging, code structure, patience and reference to documentation)
编程的最佳实践和方法(调试,代码结构,耐心和文档引用)
- Non-technical communication(empathetic and thoughtful communication)
非技术沟通的能力(同理心和有思想的交流)
- Language and computer science experience
编程语言和计算机科学经验
Our expectations
- Support each other - engineering empathy is the cirtical value at Codesmith
- Work hard, Work smart
- Thoughtful communication
JavaScript Code Execution
Frontend Masters - JavaScript the Hard Parts
- Foundations of JavaScript
- Asynchronous JavaScript(callbacks, promises)
- Iterators
- Generators & Async/await
Principles of JavaScript
In JSHP we start with a set of fundamental principles
These tools will enable us to problem solve and communicate almost any scenario in JavaScript
- We will start with an essential approach to get ourselves up to a shared level of understanding
- This approach will help us with the hard parts to come
What happens when javascript executes(runs) my code
1 | const num = 3 |
As soon as we start running our code, we create a global execution context.
- Thread of execution(parsing and executing the code line after line)
- Live memory of variables with data(known as a Global Variable Environment)
Running/calling/invoking a function
This is not same as defining a function
1 | const num = 3 |
When you execute a function you create a new execution context comprising:
- The thread of execution(we go through the code in the function line by line)
- A local memory(
Variable Environment
) where anything defined in the function is stored
We keep track of the functions being called in JavaScript with a call stack
Tracks which execution context we are inthat is, what function is currently being run and where to return to
Asynchronous JavaScript
- Foundations of JavaScript
- Asynchronous JavaScript (callbacks, promises)
- Iterators 4. Generators
Introduction Asynchronicity
Asynchornicity is the backbone of modern web development in JavaScript
JavaScript is single threaded(one command executing at a time) and has synchronous execution model(each line is executed in order the code appears)
So what if we need to wait some time before we can execute certain bits of code? Perhaps we need to wait on fresh data from an API/server request or for a timer to complete and then execute our code
We hava a conundrum - a tension between wanting to delay some code execution but not wanting to block the thread from any further code running while we wait
Solution 1
1 | function display(data) { |
Problems
- Fundamentally untenable - blocks our single JavaScript thread from running any further code while the task completes
Benefits
- It’s easy to reason about
Goals
- Be able to do tasks that take a long time to complete e.g. getting data from the server
- Continue running our JavaScript code line by line without one long task blocking further JavaScript executing
- When our slow task completes, we should be able to run functionality knowing that task is done and data is ready!
Asynchronous Web Browser APIs
Solution 2 - Introducing Web Browser APIS/Node background threads
1 | function printHello() { |
Calling the Outside World
We’re interacting with a world outside of JavaScript now - so we need rules
1 | function printHello() { |
Wrapping Up Web Browser APIs
Problems
- No problems!
- Our response data is only available in the callback function - Callback Hell
- Maybe it feels a little odd to think of passing a function into another function only for it to run much later
Benefits
- Super explic once you understand how it works under-the-hood
Asynchronous Exercise
Pair Programming
Answer these:
- I know what a variable is
- I’ve created a function before
- I’ve added a CSS style before
- I’ve implemented a sort algorithem(bubble, merge etc)
- I can add a method to an object’s prototype
- I understand the event loop in JavaScript
- I understand callback functions
- I’ve implemented filter from scratch
- I can handle collisions in hash tables
Challenges
Iterators, Generators & Async/await
Challenge 1
Let’s start by reviewing asynchronous functions! Using setTimeout, print the string ‘Hello!’ after 1000ms.
1 | function sayHello() { |
Promises
Introducing Promises
Introducing the readability enhancer - Promises
- Speical objects built into JavaScript that get returned immediately when we make a call to a web browser API/feature(e.g.
fetch
) that’s set up to return promises(not all are) - Promises act as a placeholder for the data we hope to get back from the web browser feature’s background work
- We also attach the functionality we want to defer running until that background work is done(using the built in
.then
method) - Promise objects will automactically trigger that functionality to run
- The value returned from the web browser feature’s work(e.g. the returned data from the server using
fetch
) will be that function’s input/argument
- The value returned from the web browser feature’s work(e.g. the returned data from the server using
Promises
Solution 3 - Using two-pronged ‘facade’ functions that both initiate background web browser work and return a placeholder object(promise) immediately in JavaScript
1 | function display(data) { |
1 | promiseObject { |
上面是一个promise对象的大致结构,当数据更新时,触发onFulfillment内的所有函数。
当使用fetch
方法时,会立即返回一个promise
对象(js范畴内),并且调用Web Browser API(XMLHttpRequset)在浏览器后台获取数据。当数据返回时,更新promise对象内的数据,执行方法。
Promises & Microtask Queue
But we need to know how our promise - deferred functionality gets back into JavaScrpt to be run
1 | function display(data) { |
JS除了callback queue,还有一个microtask(job) queue。fetch
的XMLHttpRequest请求完成后会将回调函数推入microtask queue而不是callback queue。event loop会先将microtask queue内的回调执行完毕后才会将callback queue内的回调推入调用栈执行。
We need a way of queuing up all this deferred functionality
Wrapping Up Promises
Problems
- 99% of developers have no idea how they’re working under the hood
- Debugging becomes super-hard
Benefits
- Cleaner readable sytle with pseudo-synchronous style code
- Nice error handling process
We have rules for the execution of our asynchronously delayed code
- Hold each promise-deferred functions in a microtask queue and each non-promise defered function in a task queue(callback queue) when the API ‘completes’
- Add the function to the Call Stack(i.e. execute the function) ONLY when the stack is totally empty(Have the Event Loop check this condition)
- Prioritize tasks(callbacks) in the microtask queue over the regular task queue
Promises, Web APIs, the Callback & Microtask Queues and Event loop allow us to defer our actions until the ‘work’(an API request, timer etc) is completed and continue running our code line by line in the meantime
Asynchronous JavaScript is the backbone of the modern web - letting us build fast ‘non-blocking’
Iterators
- Foundations of JavaScript
- Asynchronous JavaScript (callbacks, promises)
- Iterators
- Generators & Async/await
Return Function Inside a Function
We regularly have lists or collections or data where we want to go through each item and do something to each element
1 | const nums = [4, 5, 6] |
We’re going to discover there’s a new beautiful way of thinking about using each element one-by-one
Programs store data and apply functionnality to it.But there are two parts to applying functions to collections of data
- The process of acessing each element
- What we want to do to each element
Iterators automate the accessing of each element - so we can focus on what to do to each element - and make it avaliable to us in a smooth way
Imagine if we could create a function that stored numbers and each time we ran the function it would return out an element(the next one) from numbers. NOTE: It’d have to remember which element was next up somehow
But this would let us think of our array/list as a ‘stream’/flow of data with our function returning the next element from our ‘stream’ - this makes our code more readable and more functional
But it starts with us returning a function from another function
Functions can be returned from other functions in JavaScript
1 | function createNewFunc() { |
How can we run/call add2 now? Outside of createNewFunc?
Return Next Element with a Function
We want to create a function that holds both our array, the position we are currently at in our ‘stream’ of elements and has the abality to return the next element
1 | function createFunc(array) { |
How can we access the first element of our list?
By calling the returnNextElement
1 | function createFunc(array) { |
Iterator Function
The bond
- When the function inner is defined, it gets a bond to the surrounding Local Memory in which it has been defined
- When we return out inner, that surrounding live data is returned out too - attached on the ‘back’ of the function definition itself(which we now give a new global label
returnNextElement
) - When we call
returnNextElement
and don’t findarray
ori
in the immediate execution context, we look into the function definition’s ‘backpack’(closure) of persiitent live data - The ‘backpack’ is offically known as the C.O.V.E.(Cloesd Over Variable Environment) or ‘closure’
returnNextElement
has everything we need all bundled up in it
- Our underlying array itself
- The position we are currently at in our ‘stream’ of elements
- The ability to return the next element
This relies completely on the special property of fucntions in JavaScript that when they are born inside other functions and returned - they get a backpack(closure)
What is the posh name for returnNextElement
?
So iterators turns our data into ‘streams’ of actual values we can access one after another
Now we have functions that hold our underlying array, the position we’re currently at in the array, and return out the next item in the ‘stream’ of elements from our array when run
This lets us have for loops that show us the element itself in the body on each loop and more deeply allows us to rethink arrays as flows of elements themselves which we can interact with by calling a function that switches that flow on to give us our next element
We have truly ‘decoupled’ the process of accessing each element from what we want to do to each element
Iterators Exercise
Generators
- Foundations of JavaScript
- Asynchronous JavaScript (callbacks, promises)
- Iterators
- Generators & Async/await
Generators
JavaScript built in iterators are actually objects with a next method that when called returns the next element from the ‘stream’/flow - so let’s restructure slightly
1 | function createFlow(array) { |
And the built in iterators actually produce the next element in the format: {value: 4}
Once we start thinking of our data as flows(where we can pick of an element one-by-one) we can rethink how we produce those flows - JavaScript now lets us produce the flows using a function
1 | function *createFlow() { |
What do we hope returnNextElement.next()
will return? But how?
Generator Functions with Dynamic Data
This allows us to dynamically set what data flows to us(when we run returnNextElement’s function)
1 | function *createFlow() { |
returnNextElement
is a special object(a gengerator object) that when its next method is run starts(or continues) running createFlow
until it hits yield
and returns out the value being ‘yielded’
1 | function *createFlow() { |
returnNextElement.next()
的参数会作为上一个yield
的执行结果的值
具体来说,returnNextElement.next(2)
会使*createFlow
的const newNum = yield num
变为const newNum = 2
We end up with a ‘stream’/flow of values that we can get one-by-one by running returnNextElement.next()
Introducing Async Gererators
And most importantly, for the first time we get to pause(‘suspend’) a function being run and then return to it by calling returnNextElement.next()
In asynchronous JavaScript we want to
- Initiate a task that takes a long time(e.g. requesting data from the server)
- Move on to more synchronous regular code in the meantime
- Run some functionality once the requested data has come back
What if we were to yield
out of the function at the moment of sending off the long-time task and return to the function only when the task is complete
Async Generators
We can use the abality to pause createFlow’s running and then restart it only when our data returns
1 | function doWhenDataReceived(value) { |
We get to control when we return back to createFlow and continue executing - by setting up the trigger to do so(returnNextElement.next()
) to be run by our function that was triggered by the promise resolution(when the value retruned from twitter)
Final
Async Await
Async/await simplifies all this and finally fixes the inversion of control problem of callbacks
1 | async function createFlow() { |
No need for a triggered function on the promise resolution, instead we auto trigger the resumption of the createFlow
execution(this functionality is still added to the microtask queue though)