Asynchrony: Under the Hood - Shelley Vohr - JSConf EU

JSConf
27 Jun 201825:24

Summary

TLDRIn this talk, Shelley, a software engineer at GitHub, delves into asynchronous programming in JavaScript. She begins with basic principles and progresses to methodologies, highlighting the event loop, call stack, and run-to-completion semantics. Shelley contrasts callbacks, promises, generators, and async/await, explaining their roles and best use cases. She emphasizes the cognitive challenges of callbacks, the error handling in promises, and the intuitive, synchronous-like code structure of async/await. The presentation aims to equip developers with a deeper understanding of asynchronous tools in JavaScript.

Takeaways

  • 🕒 Asynchronous programming in JavaScript deals with the 'now' and 'later' execution of code, managing tasks that will complete at an uncertain time.
  • 🔁 The event loop is a central concept, running as an endless loop that processes chunks of code and manages the execution queue.
  • 🆕 ES6 introduced microtasks, a queue that precedes the traditional task queue and helps in handling asynchronous operations more promptly.
  • 💡 JavaScript's run-to-completion semantics ensure that a task finishes before the next begins, providing a safe execution context without concurrent modifications.
  • 📈 Understanding the call stack is crucial for tracking function execution and returns, which is vital for managing asynchronous callbacks.
  • 🔀 Callbacks, while simple, can lead to 'callback hell' due to their non-linear and complex nature, making code hard to follow and error-prone.
  • 🔐 Promises introduced a new level of abstraction with placeholders for future values, offering better control and error handling for asynchronous operations.
  • 🔁 Generators allow for a pause and resume pattern, making asynchronous code appear synchronous and simplifying error handling with traditional try/catch.
  • 🔄 Async/await syntax, built on promises, makes asynchronous code more readable and manageable, using synchronous-style error handling with try/catch.
  • 🛠️ Choosing the right asynchronous pattern depends on the specific requirements of the task, with each method having its strengths and appropriate use cases.

Q & A

  • What is the main focus of Shelley's talk?

    -Shelley's talk focuses on the mechanics of asynchronous programming in JavaScript, including the basic principles, methodologies, and nuances that differentiate them.

  • What does the term 'asynch' refer to in the context of programming?

    -In the context of programming, 'asynch' refers to asynchronous operations, which are operations that do not occur in a linear, sequential order but are managed over time with respect to 'now' and 'later'.

  • How is the event loop described in the talk?

    -The event loop is described as an endlessly running, singly threaded loop that processes tasks in the order they are added to the queue, with each iteration running a small chunk of code.

  • What is the significance of JavaScript's run-to-completion semantics?

    -JavaScript's run-to-completion semantics mean that the current task always finishes before the next task begins, ensuring that each task has complete control over all current states without interference from other tasks.

  • What is the role of the call stack in JavaScript's execution model?

    -The call stack manages the execution of functions by keeping track of the location each function needs to return to once it's done executing, allowing for proper sequencing and memory management of function calls.

  • Why can callbacks lead to difficulties in understanding and managing asynchronous code?

    -Callbacks can lead to difficulties because they require managing multiple asynchronous operations that may not execute in a linear, sequential order, which can result in complex and hard-to-follow code structures known as 'callback hell'.

  • What is the 'inversion of control' in the context of callbacks?

    -Inversion of control in callbacks refers to the practice where the flow of the program is dictated by callback functions rather than the sequential order of the code, which can lead to trust issues and complexity.

  • How do promises change the way asynchronous operations are handled in JavaScript?

    -Promises introduce a new way of handling asynchronous operations by providing a placeholder for future values, allowing developers to work with these values as if they were already available, and managing the eventual success or failure of these operations.

  • What is the microtask queue and how does it relate to promises?

    -The microtask queue is a queue introduced in ES6 that allows certain asynchronous actions to be added to the end of the current microtask queue instead of waiting for the next event loop tick, which is particularly useful for promises to ensure their callbacks are executed immediately after the promise settles.

  • How do generators provide a different approach to asynchronous programming?

    -Generators allow for a function to be paused and resumed multiple times, which enables a synchronous-looking code style while hiding the asynchronous operations as implementation details, making the code easier to reason about.

  • What is the main benefit of using async/await over traditional promises?

    -Async/await simplifies the process of working with promises by making asynchronous code look and behave more like synchronous code, improving error handling and readability, and reducing the amount of boilerplate code needed.

Outlines

00:00

💻 Introduction to Asynchronous Programming in JavaScript

Shelley, a software engineer at GitHub, introduces the topic of asynchronous programming in JavaScript. She emphasizes the importance of understanding the relationship between 'now' and 'later' in code execution and the challenges it presents for developers. Shelley outlines the event loop as a central concept, describing it as a continuously running, single-threaded loop that processes chunks of code. She also introduces the idea of JavaScript's run-to-completion semantics, where tasks execute in full before yielding control back to the event loop. The summary also touches on the mechanics of the call stack and how functions are managed within it.

05:01

🔁 Understanding Callbacks and Their Challenges

This section delves into callbacks, a common asynchronous pattern. Shelley illustrates how callbacks can lead to complex code structures, known as 'callback hell,' which are difficult to follow and maintain. She discusses the inversion of control, where the flow of the program is handed off to callbacks, creating trust issues. Shelley also addresses error handling in callbacks, emphasizing the need to handle errors both through callbacks and exceptions. The introduction of Promises with ES6 is highlighted as a significant innovation that changed how asynchronous programming is approached in JavaScript, offering a new way to handle tasks and their outcomes.

10:03

🔗 Promises: Enhancing Asynchronous Code Management

Shelley explains how Promises revolutionized JavaScript's approach to asynchronous operations. Promises act as placeholders for future values, which can either be fulfilled or rejected. They allow for a more manageable way to handle asynchronous operations by providing a clearer sequence of operations with the 'then' and 'catch' methods. She discusses the microtask queue, which is part of the event loop and allows for immediate execution of tasks associated with Promises. The section also covers how Promises help avoid the callback 'black box' issue and improve error handling with catch handlers, making asynchronous code more predictable and easier to debug.

15:06

🔄 Generators: A Synchronous Approach to Asynchronous Code

Generators are introduced as a way to write asynchronous code that looks synchronous. Shelley describes generators as functions that can be paused and resumed, allowing other code to run in between. This feature makes generators particularly useful for creating a clear and linear code structure while handling asynchronous operations. She demonstrates how the 'yield' keyword is used within generator functions to send and receive values, and how this mechanism can be used to iterate over asynchronous operations in a more controlled manner. The summary also touches on the error-handling capabilities of generators, which can use traditional try-catch blocks, simplifying the process compared to other asynchronous patterns.

20:07

🚀 Async/Await: Simplified Asynchronous Code with Promises

The final section focuses on async/await, a syntactic sugar built on top of Promises that simplifies writing asynchronous code. Shelley explains that async/await allows developers to write code that looks synchronous but operates asynchronously under the hood. She points out the benefits of using async/await for better error handling with a single try-catch block and for writing more readable and maintainable code. The section also discusses the potential pitfall of forgetting the asynchronous nature of the code when using async/await and the importance of understanding the underlying mechanisms for effective usage.

25:09

🎉 Conclusion and Q&A

Shelley concludes the presentation with a summary of the key points discussed, emphasizing the importance of choosing the right asynchronous pattern for the job at hand. She highlights the strengths and weaknesses of callbacks, Promises, generators, and async/await, and encourages a deeper understanding of these tools to write more effective and maintainable JavaScript code. The section ends with applause from the audience, indicating the successful delivery of the presentation.

Mindmap

Keywords

💡Asynchronous Programming

Asynchronous programming refers to the way in which programs handle operations that are not immediately completed, allowing the program to continue running while waiting for the operation to finish. In the video, Shelley discusses the intricacies of asynchronous programming in JavaScript, emphasizing the importance of understanding the relationship between 'now' and 'later' in code execution. This concept is central to the video's theme, as it underpins the various methodologies and tools used to manage asynchronous tasks.

💡Event Loop

The event loop is a programming construct that allows for asynchronous programming by queuing tasks to be executed once the call stack is clear. Shelley explains it as an endlessly running, singly threaded loop that processes tasks in the order they are added to the queue. This concept is crucial for understanding how JavaScript handles asynchronous operations, as it illustrates the timing and order in which tasks are executed.

💡Microtasks Queue

Introduced with ES6, the microtasks queue is a part of the JavaScript engine that handles high-priority tasks immediately after the current task completes. Shelley mentions that promises utilize the microtasks queue, which is different from the general task queue. This feature allows for more immediate execution of certain asynchronous tasks, which is a significant advancement in JavaScript's asynchronous capabilities.

💡Run-to-Completion Semantics

This term describes JavaScript's behavior where the current task must complete before the next task begins, or until it yields control back to the event loop. Shelley uses this concept to explain how tasks have complete control over the current state without interference from other tasks. It's a fundamental principle that affects how developers write and understand asynchronous code.

💡Call Stack

The call stack is a data structure that keeps track of active function invocations, or 'calls'. Shelley explains it in the context of function execution, where each function call adds an entry to the stack, and when the function is done, the entry is removed. Understanding the call stack is essential for grasping how function calls are managed and how they relate to asynchronous operations.

💡Callbacks

Callbacks are functions that are to be executed after another function has completed its execution. Shelley discusses how callbacks fit into the asynchronous landscape, noting that they can lead to 'callback hell' when nested too deeply. Callbacks are a traditional way to handle asynchronous operations in JavaScript, but they can become complex and difficult to manage.

💡Promises

Promises are objects in JavaScript that represent the eventual completion or failure of an asynchronous operation. Shelley explains that promises act as placeholders for future values and change how developers handle asynchronous operations by providing a more structured way to manage success and failure scenarios. They are a key innovation in JavaScript's approach to asynchronous programming.

💡Generators

Generators are a special type of function in JavaScript that can be paused and resumed, allowing for a more synchronous-looking code style while handling asynchronous operations. Shelley touches on generators as a way to hide asynchrony and write code that appears to run synchronously but can handle asynchronous tasks. They are an alternative approach to managing asynchronous flow in JavaScript.

💡Async/Await

Async/await is a syntactic feature in JavaScript that allows writing asynchronous code that looks and behaves a bit more like synchronous code. Shelley describes async/await as syntactic sugar on top of promises, making it easier to write and understand asynchronous code. It simplifies error handling and allows for a more linear and readable code structure.

💡Error Handling

Error handling in asynchronous programming is a critical aspect that Shelley addresses in the context of promises and async/await. She explains how errors in asynchronous operations can be handled using try/catch blocks, which is a departure from the more complex error handling required with callbacks. Proper error handling is essential for robust asynchronous code and is a significant part of the video's discussion.

Highlights

Introduction to asynchronous programming in JavaScript by Shelley, a software engineer at GitHub.

Explaining the basic principles of asynchronous programming and its importance in managing the 'now and later' relationship in code.

The event loop as an endlessly running single-threaded loop that manages code execution in JavaScript.

Introduction of the microtasks queue with ES6 and its role in asynchronous programming.

JavaScript's run-to-completion semantics and their impact on task execution.

Explanation of the call stack and how it manages function execution and return addresses.

The concept of inversion of control and its challenges in asynchronous programming with callbacks.

The issue of error handling in callbacks and the need for proper error management.

Promises as a new concept in ES6 that changed the JavaScript engine's approach to asynchronous operations.

How promises act as placeholders for future values and their role in error handling.

The microtask queue's function in handling promises and its impact on the event loop.

Generators as a way to write asynchronous code with a synchronous-looking style.

The use of the 'yield' keyword in generator functions to pause and resume execution.

Async/await as a syntactical approach to simplify working with promises and writing asynchronous code.

How async/await functions return promises implicitly and their impact on error handling.

The importance of choosing the right asynchronous programming tool for the job based on its specific requirements.

Conclusion and summary of the key takeaways from the talk on asynchronous programming in JavaScript.

Transcripts

play00:09

Hi everybody ! Thank you again for the introduction

play00:13

I'm Shelley, a software engineer at GitHub working on open source.

play00:18

Today, I would like to do a deep dive into the mechanics of asynchronous programming

play00:24

in JavaScript.

play00:25

To make sure we're all on the same page, before I get started, I will go over basic principles

play00:31

of asynch, and then we will launch into the different methodologies and nuances of what

play00:37

exactly differentiates them, and, finally, we will compare over the spectrum of options

play00:43

to discuss which situations require which approaches.

play00:48

Let's say you have a programme detained within a contained within a final file.

play00:53

It's mate up of functions.

play00:54

At any given time, only one of these is going to be executing now.

play01:00

The rest will execute later at some point in the future.

play01:04

That, there, is the crux of asynch.

play01:08

It's the relationship between now and later, and the way in which you manage this relationship

play01:13

with your code.

play01:15

What is key here, however, and what causes some of the most difficulties for developers

play01:20

when they're just starting out, is that later does not mean strictly and immediately after

play01:26

now.

play01:28

It could be at any point in the future, and you don't necessarily know when that will

play01:32

be.

play01:33

Now, let's move on to some of the technical underpinnings of how code is executed in the

play01:38

JavaScript environment.

play01:40

In pursuit of this understanding, it's best to start with the event loop.

play01:45

The event loop can best be conceptualised as an endlessly running singly threaded loop

play01:51

where each iteration runs a small chunk of code in the programme.

play01:56

If you want to run a chunk of code at a later time, that chunk would simply be added to

play02:01

the queue for the event loop.

play02:04

When the time came that you desired it to execute, it would be dequeued and executed.

play02:10

With ES6 came a new concept called the microtasks queue, but we will save that for a little

play02:15

bit later.

play02:16

It is also important to mention that JavaScript has what is known as run-to-completion semantics.

play02:24

This means that the current task always finishes before the next task begins.

play02:30

Or until it explicitly yields control back to the event loop.

play02:35

As a result of this, each task has complete control over all current states, and does

play02:41

not need to worry about another task modifying things at the exact same time.

play02:47

Looking at the code here, running this will always print first and then second, because

play02:52

the function, starting in setTimeout is added to the task queue immediately but only executed

play03:00

after the current piece of code is down since it yielded control.

play03:04

Now let's look at the call stack.

play03:07

When a function foo calls a function bar, bar needs to know where inside foo to return

play03:15

to after it's done.

play03:18

This information is managed with the call stack.

play03:21

Take a look at the set of three functions on the left-hand side of the slide.

play03:26

Let's walk through how the stack will look as the programme executes.

play03:30

Initially, when the programme above has started, the call stack is empty.

play03:37

After foo is called, the stack has one entry: location and global scope.

play03:42

As I mentioned before, foo knows where to return to when it has finished.

play03:47

Next, after bar is called, the stack has two entries.

play03:52

It's now stored at the location that bar needs to return to when it has finished executing

play03:56

in addition to global scope.

play03:59

The trend continues as bas is called, containing the previous return address and the previous

play04:05

return addresses from previous calls.

play04:09

When console.log is called in bas, the call stack is... the console.

play04:15

Next, each of the functions terminates, and, each time, the top entry is removed from the

play04:21

stack.

play04:22

After foo is done, we're back in global scope and the call stack is empty.

play04:27

At the end, we return, and the programme is terminated.

play04:34

In looking at this, you can see a handful of the things I previously mentioned.

play04:40

The event loop, the task queue, and the stack.

play04:44

The current task comes off of the event queue and its location is stored in memory while

play04:50

irrelevant variables populate the heap.

play04:54

The task queue is populated by task sources, any one of which would be a particular chunk

play05:00

of code in an executing programme.

play05:04

This snapshot would represent the moment after the all functions in the previous slide had

play05:09

executed and before locations had begun to pop off of the stack.

play05:15

Most of you have likely encountered callbacks, so I'm going to focus more on how they fit

play05:21

into the asynch landscape as a whole.

play05:24

Let's start by looking at some functions and discussing them in context.

play05:29

Here, you can see I have two programmes side by side.

play05:35

On the left, the function names are alphabetical from top down, and, on the right side, I've

play05:41

mapped the functions to the order in which they run.

play05:45

Most likely, your eyes have to do a significant amount of jumping around in order to discern

play05:52

the order in which the functions are executing for the programme on the left.

play05:57

Since they're not running in the top-down sequential order you might expect.

play06:01

By following the Cardinal number of words on the right side, you probably had an easier

play06:07

time.

play06:08

Let's talk about why that is.

play06:11

Your brain operates sequentially.

play06:13

It places it at odds with the inherent functionality of callbacks.

play06:19

This is a comparatively simpler example, but when callbacks start to next significantly,

play06:24

you enter what is known colloquially as callback hell which becomes more difficult for your

play06:30

brain to reason of through.

play06:32

What is happening here in terms of the call stack, and, by extension, the event loop?

play06:38

Assuming all these functions are asynch, first we will begin, and then immediately yield

play06:43

control, and, then second, execute.

play06:46

A callback was registered by first so the next available task will be that callback.

play06:52

Next, third will execute, followed by fourth.

play06:56

Fourth registers a callback, at then returns control, so that fifth executes.

play07:01

The callback registered by fourth will come off the Q and execute, and — the queue and

play07:05

execute, and finally, six will execute.

play07:09

For each of these functions taking a callback, the callback itself is a black box for the

play07:15

function.

play07:16

The continuation of the programme is dependent on our handing that callback off to another

play07:23

part of the code and then essentially just praying that it will do the correct thing

play07:27

whether the callback is invoked.

play07:30

This paradigm is known as inversion of control, and can create significant trust issues.

play07:38

In the previous code snippet I had up, you could see that your eyes had to skip around,

play07:44

even though they most likely wanted to read the code in a top-down fashion.

play07:49

When you sometimes struggle to understand how and when each part of a piece of code

play07:53

is working, it's undoubtedly hard to deal with errors in that code been within the callback

play08:00

landscape, there are two ways in which errors are reported: via callbacks and via exceptions.

play08:06

You need to combine both properly.

play08:09

This is because the most obvious, but occasionally overlooked aspect of dealing with errors in

play08:14

callbacks, is to make sure that they've actually all been handled.

play08:18

However, there's a caveat to this advice, and it lies specifically in how errors are

play08:23

caught: can you see how, in the snippet above, I returned and didn't throw an error.

play08:29

That is intentional.

play08:35

In an asynchronous environment, the exception could be thrown, and, when the lock is out

play08:40

of scope, the error would be rendered meaningless.

play08:45

The next big innovation in asynchronous programming promises hit JavaScript with ES6.

play08:52

Before this, there was no direct notion of asynch built directly into the JavaScript

play08:57

engine.

play08:59

All it ever did was execute a single chunk of your programme at any given moment when

play09:04

asked to.

play09:06

You, the developer, asked it to by means of callbacks or timeouts, in order to shuffle

play09:12

its place in the event loop.

play09:15

With promises came a change to the JS engine which took a form of the queue that I mentioned

play09:20

briefly at the beginning — the microtask queue.

play09:24

Before I delve into how exactly this changes the form of the stack queue event loop diagram

play09:30

I showed you a few slides ago, let's look at an example of a programme utilising promises

play09:36

and then discuss what is happening and why.

play09:41

Promises act as placeholders.

play09:44

They allow us to reason about future values without necessarily knowing their outcomes,

play09:49

making them functionally extemporaneous.

play09:52

When the future value is settled, it might succeed or fail.

play09:56

Then, at that point, the promise is no longer a placeholder and becomes an immutable value.

play10:02

A few slides ago, I referenced the paradigm, the version of control.

play10:11

Promises confer to us a capability to know when a given task finishes.

play10:23

Promises may seem like an entirely different paradigm, but they don't get rid of callbacks

play10:28

at all.

play10:29

They just change with the callback has passed to, and remove the black box effect we saw

play10:35

previously.

play10:36

We get back from a function when it is completed, and then perform new passes from there, as

play10:42

I said.

play10:44

Promises are not just the mechanism for single-step operations that follow with this then that

play10:53

flow.

play11:01

[Sound distorted].

play11:17

From the — calls for

play11:40

the second then would create then another promise.

play11:47

So, how does this look under the hood?

play11:51

Promises utilise the microtask queue by allows us to say, "Here is this thing that I need

play11:58

to do later but I need to ensure it happens immediately later from the — the microtask

play12:06

queue can best be thought of as attached to each tech in the event loop.

play12:11

Some asynch actions will be added to the end of the current microtask queue instead of

play12:17

creating a whole new tick as a whole.

play12:19

When a promise is resolved or rejected, the associated handlers will be called asynchronously

play12:27

as microtasks, and then added to the queue for the current tick.

play12:33

This diagram looks nearly identical to the diagram I showed you a few slides back.

play12:38

One key difference from the microtask queue.

play12:41

Once a promise settles or already settling, it queues a microtask.

play12:48

This ensures promise callbacks are asynch even if the promise has already settled, so

play12:55

calling it then would resolve a reject against the settled promise immediately queues a microtask.

play13:01

Any additional microtasks queued during a given microtask are added to the end of the

play13:07

queue and also processed.

play13:09

So, in looking at the diagram, you see that the current task can come off the task queue

play13:15

or the microtask queue.

play13:18

All promised callbacks are queued as microtasks.

play13:21

A microtask can also cause one or more microtasks to be added to the end of the same queue.

play13:27

So it's theoretically possible that a microtask loop could spin indefinitely thus starving

play13:32

the programme of its ability to move on to the next event loop tick.

play13:37

This would conceptually be almost the same as expressing an infinite loop in your code.

play13:44

What happens if something goes wrong in a promise.

play13:46

How do we deal with those errors?

play13:48

By default, it turns out, promises are swallowed if unhandled.

play13:53

More specifically, any exception which is thrown inside a dot.then handler, or within

play14:01

a function path, a new promise will be soundly disposed of unless manually handled.

play14:08

The standard way to handle errors from promises is to add a catch handler at the end of your

play14:13

promise chain.

play14:15

You can also chain these handlers so that you can throw errors into progressively more

play14:19

outer level scopes.

play14:21

Catching and rethrowing errors in this way will tell you what led to the error more effectively

play14:27

so that you can later debug the issue more efficiently.

play14:31

If exceptions are thrown inside the callbacks of dot then and dot catch, it's not a method

play14:37

because these two can revert them to rejections.

play14:41

Let's look at an example and talk about what would work and what wouldn't in more concrete

play14:46

terms.

play14:47

This is a simple promise example.

play14:50

From looking, you can see that from is going to reject with an error, no matter what.

play14:56

So, what happens in the first example versus the second?

play15:00

The first has a catch.

play15:02

So it's going to print the error followed by the error message which in this case is

play15:05

rejected.

play15:07

You can also see the associated call stack by printing error.stack.

play15:11

The second one doesn't have a catch.

play15:13

So even though we threw an error, it will simply fail silently.

play15:19

This next type of asynch approach — generators — sits a little on the edge of the overall

play15:24

asynch landscape but is nevertheless important to touch on.

play15:29

The first thing to observe as we talk about generators, is how they differ from normal

play15:34

functions.

play15:35

There are several notable differences but key to generators is their behaviour with

play15:40

respect to the run to completion expectation.

play15:44

With generators, we have a different kind of function which can be paused in the middle

play15:48

either once or several times.

play15:51

It is resumed later which allows other code to run during these pause periods between

play15:57

runs.

play15:59

The main strength of generators is that they provide a single threaded synchronous-looking

play16:03

code style but allow us to hide the asynchrony away as an implementation detail.

play16:11

This lets us express naturally what a programme step and statement flow is without navigating

play16:17

the asynchronous gotchas and syntax at the same time.

play16:22

To get an idea of how this looks in practice, let's see a function.

play16:27

Here, you will see immediately that there's a little star next to the function in the

play16:32

signature.

play16:33

This is the syntactical indicator that you're looking at a generator function.

play16:38

The key word "yield" will send out a value whenever the function is run.

play16:42

So, here, the yield index plus plus expression will send the index value out when pausing

play16:49

the generator function at that point.

play16:51

If ever the generator is restarted, whatever value it was sent in will be the result of

play16:57

that expression.

play16:58

It will then get added to one and assigned to the index variable.

play17:03

When we start, index is zero.

play17:06

So this will be yielded and then 1 will be added to the index as a result of the plus

play17:10

plus.

play17:12

This will occur each time as the value of index is passed out and then in again, and

play17:18

so that it increments by one every time.

play17:21

To see this a little more clearly, let's look at a diagrammatical representation.

play17:26

On the left, you will see a traditional non-generator function.

play17:29

It adheres to the run to completion behaviour we expect from functions with no interruptions

play17:34

from beginning to end.

play17:36

On the right is the generator function.

play17:39

With multiple stops and starts between beginning and end.

play17:43

It's important to note that there is no real official end per se in the generator function.

play17:48

The end in this case will be the last time that that yield is called.

play17:53

When the generator function is initialised, an iterator is returned, and then the generator

play17:58

starts with the first call to next on the function.

play18:02

This function pauses when yield is called, and then would restart with the next call

play18:07

to next, and so on, and so forth.

play18:10

To recap, with normal functions, you get parameters at the beginning, and a return value at the

play18:14

end, but generator functions, you send messages out with yield, and you send messages back

play18:20

in with each restart.

play18:22

One of the most powerful parts of ES6 generator design is that the semantics of the code inside

play18:28

the generator are synchronous.

play18:30

Even if the external iteration control proceeds asynchronously.

play18:34

That's a fancy way of saying that you can use a very simple error-handling technique

play18:38

you're probably familiar with — the tri catch mechanism.

play18:41

If even if the function will pause at the yield expression, the tri catch with catch

play18:49

it.

play18:50

With normal asynch capabilities like callbacks, that's almost impossible to do.

play18:55

The generator itself has a throw function to throw an error into the generator at its

play19:01

pause position which of course can also be caught by a try catch inside the generator.

play19:08

Asynch await is a new way to write asynchronous code.

play19:11

Previous options are callbacks and promises.

play19:14

Asynch await was created to simplify the process of working with and writing chained promises.

play19:21

And so asynch await functions themselves return promises.

play19:25

They cannot be used with plain callbacks.

play19:27

Asynch await is thus like promises non-blocking, and makes asynchronous code look and behave

play19:34

more like synchronous code.

play19:36

This is where all the power lies.

play19:39

Since any asynch await function returns a promise implicitly, the resolve value of the

play19:44

promise will be whatever you return from the function to ill straight, let's look at.

play19:50

Here, we are returning a string that returns several components of a street address.

play19:57

The function has the asynch key word before it and the await key word can only be used

play20:03

in function s defined with asynch.

play20:06

All four variables must be resolved before the function will return the desired string.

play20:12

Also important to note here is that if we did not use the await key cord before each

play20:17

of the address component functions, this would not necessarily fail.

play20:22

It would just mean that the variables would be set to promises and from the values returned

play20:26

from them.

play20:27

There is it also an oft overlooked gotcha.

play20:34

Await calls operate sequentially.

play20:37

What this means is that the call to get.city won't kick off until we have a value for get.street

play20:44

address.

play20:45

In some, this depends on a call from results of a previous call.

play20:53

Here, we want to get the address components simultaneously, so we should not be blocking

play20:59

each on the previous one.

play21:02

Instead, we should rewrite it like this.

play21:06

Under the hood, asynch await works almost exactly the same as promises, utilising the

play21:12

new microtask queue introduced to handle asynch operations.

play21:16

If you're familiar with promises, you know that, if a promise is rejected, you need to

play21:23

handle that error inside a catch.

play21:26

If a handle be errors for synchronous and asynchronous code, you will likely have to

play21:31

duplicate your error-handler.

play21:33

In the above snippet, we can see there is duplicate code on lines 6 and 8.

play21:40

The catch statement on line 7 will handle any errors that the synchronous function do

play21:45

synchronous things may throw, but it won't handle any errors thrown by do something since

play21:50

it's asynchronous.

play21:54

This example may seem palatable, since all it's doing is printing the error to console,

play22:00

but if there is any kind of complex error-handle be logic, we want to avoid duplicating it.

play22:06

Asynch await let's us do exactly that.

play22:10

But looking at the code snippet on the top right, you will see that we only catch once.

play22:16

When we use asynch await, we can catch errors in a single handler.

play22:24

Here, we can both minimise the total amount of code that we write and catch errors in

play22:29

a more readable and clear way.

play22:32

So, wrapping up: our brains plan things out in sequential blocking singly threaded semantic

play22:43

ways.

play22:44

But callbacks express asynchronous flow in a rather non-linear and non-sequential way.

play22:52

Which makes reasoning properly about such code much more difficult.

play22:57

Callbacks suffer from lack of sequentiality and lack of trustability, but they're good

play23:03

in situations where you may just be performing a simple request that is always asynchronous.

play23:09

They're just plain functions, so they also don't require any additional understanding

play23:14

beyond knowing how asynchronous operations work.

play23:17

They also at the end to be more verbose, so co-ordinating multiple asynchronous requests

play23:22

with them concurrently can lead to callback hell if you're not actively modularising your

play23:28

functions.

play23:29

Dealing with errors also tends to be more confusing, since there could be many error

play23:34

objects that all go back to a single error further down the call stack.

play23:40

Promises are easier to reason of about.

play23:43

Although they still have their downsides.

play23:45

There are they are especially useful for co-ordinating multiple asynchronous operations, propagating

play23:52

errors from nested asynch operations, and dynamically chaining asynchronous operation

play23:59

s, i.e., those where you would do something asynch, examine output, and then do something

play24:04

else asynch, based on the intermediate values.

play24:08

Errors and error stacks in promises can also be a challenge, because they behave unintuitively

play24:15

in the way that they print errors when those errors are thrown.

play24:20

They brought new changes to the JavaScript engine and paved the way for later innovations

play24:24

like asynch await.

play24:27

This latest asynch pattern is basically syntactical sugar on promises, but it allows for more

play24:33

intuitive reeled of asynchronous code.

play24:36

It improves error handling compared to traditional promises and can be handled with simple try

play24:42

catch blocks.

play24:43

It is so easy to use asynch await that one of its slight dangers that you forget you're

play24:48

writing asynchronous code at all.

play24:52

Ultimately, the best tool to use for a job is going to be the one that is most specific

play24:58

to the job that you're doing.

play25:00

Now, I hope you'll have a firmer grasp on what exactly makes each of these tools a best

play25:06

fit for certain jobs.

play25:08

And be able to use them with the deeper understanding of what is going on at a granular level.

play25:13

Thank you very much.

play25:15

[Applause]

Rate This

5.0 / 5 (0 votes)

Связанные теги
JavaScriptAsynchronousEvent LoopPromisesAsync/AwaitSoftware EngineeringGitHubCallback HellError HandlingCode Execution
Вам нужно краткое изложение на английском?