Javascript Event Listener Wait for Function Before Firing Event Again
When does an asynchronous function end? And why is this such a hard question to answer?
Well it turns out that understanding asynchronous functions requires a neat deal of knowledge about how JavaScript works fundamentally.
Permit's go explore this concept, and acquire a lot almost JavaScript in the process.
Are you ready? Let'southward go.
What is asynchronous code?
By design, JavaScript is a synchronous programming linguistic communication. This means that when code is executed, JavaScript starts at the meridian of the file and runs through code line by line, until information technology is washed.
The issue of this design decision is that but one thing tin can happen at any in one case.
You lot tin can think of this every bit if you were juggling vi small balls. While you are juggling, your hands are occupied and can't handle annihilation else.
Information technology'southward the same with JavaScript: once the code is running, it has its easily full with that code. We telephone call this this kind of synchronous code blocking. Because it's effectively blocking other code from running.
Let'south circle dorsum to the juggling example. What would happen if you wanted to add another ball? Instead of six assurance, yous wanted to juggle seven balls. That'due south might be a problem.
You don't want to finish juggling, considering it'southward merely so much fun. Only y'all can't go and get some other ball either, because that would hateful y'all'd accept to stop.
The solution? Delegate the work to a friend or family member. They aren't juggling, so they can go and get the ball for y'all, then toss it into your juggling at a fourth dimension when your hand is gratuitous and you are ready to add together another ball mid-juggle.
This is what asynchronous code is. JavaScript is delegating the work to something else, then going about it's own business. Then when it's ready, it will receive the results back from the work.
Who is doing the other work?
Alright, so nosotros know that JavaScript is synchronous and lazy. Information technology doesn't want to do all of the piece of work itself, and then it farms it out to something else.
But who is this mysterious entity that works for JavaScript? And how does information technology get hired to work for JavaScript?
Well, permit's take a look at an example of asynchronous lawmaking.
const logName = () => { console.log("Han") } setTimeout(logName, 0) console.log("Hi there")
Running this code results in the following output in the console:
// in console How-do-you-do in that location Han
Alright. What is going on?
It turns out that the way we farm out work in JavaScript is to employ environment-specific functions and APIs. And this is a source of great confusion in JavaScript.
JavaScript always runs in an surroundings.
Ofttimes, that environment is the browser. Only it can likewise be on the server with NodeJS. Only what on earth is the deviation?
The difference – and this is important – is that the browser and the server (NodeJS), functionality-wise, are non equivalent. They are often similar, only they are not the same.
Let's illustrate this with an example. Allow's say JavaScript is the protagonist of an epic fantasy book. Just an ordinary farm kid.
Now let'southward say that this subcontract child establish two suits of special armor that gave them powers across their own.
When they used the browser suit of armor, they gained admission to a certain prepare of capabilities.
When they used the server accommodate of armor they gained access to another set of capabilities.
These suits have some overlap, because the creators of these suits had the aforementioned needs in certain places, but not in others.
This is what an environment is. A place where code is run, where there exist tools that are built on top of the existing JavaScript language. They are non a role of the language, simply the line is frequently blurred because we use these tools every solar day when we write code.
setTimeout, fetch, and DOM are all examples of Web APIs. (You lot can run into the full list of Web APIs here.) They are tools that are built into the browser, and that are made available to us when our code is run.
And because we ever run JavaScript in an environment, it seems like these are part of the language. Simply they are not.
So if you've e'er wondered why you can use fetch in JavaScript when you run it in the browser (but need to install a package when you run it in NodeJS), this is why. Someone thought fetch was a good thought, and built information technology as a tool for the NodeJS environment.
Confusing? Yes!
But now nosotros can finally sympathise what takes on the work from JavaScript, and how it gets hired.
It turns out that it is the environment that takes on the work, and the manner to get the environs to do that work, is to employ functionality that belongs to the environs. For example fetch or setTimeout in the browser environs.
What happens to the work?
Swell. So the environment takes on the work. Then what?
At some bespeak y'all need to get the results back. But allow's recollect well-nigh how this would work.
Let's go back to the juggling case from the kickoff. Imagine you asked for a new ball, and a friend just started throwing the ball at you when you lot weren't fix.
That would be a disaster. Maybe you lot could become lucky and catch it and become it into your routine effectively. Only theres a large take chances that it may cause you to drop all of your balls and crash your routine. Wouldn't it be better if you gave strict instructions on when to receive the ball?
As it turns out, there are strict rules surrounding when JavaScript can receive delegated work.
Those rules are governed past the outcome loop and involve the microtask and macrotask queue. Yeah, I know. Information technology's a lot. But acquit with me.
Alright. So when we delegate asynchronous code to the browser, the browser takes and runs the lawmaking and takes on that workload. Just there may exist multiple tasks that are given to the browser, so we demand to brand sure that we tin can prioritise these tasks.
This is where the microtask queue and the macrotask queue come in play. The browser will accept the work, do information technology, and then identify the event in one of the two queues based on the type of work it receives.
Promises, for case, are placed in the microtask queue and have a higher priority.
Events and setTimeout are examples of work that is put in the macrotask queue, and have a lower priority.
Now once the work is done, and is placed in 1 of the ii queues, the event loop will run back and forth and bank check whether or non JavaScript is set up to receive the results.
Only when JavaScript is washed running all its synchronous code, and is skilful and ready, volition the event loop first picking from the queues and handing the functions dorsum to JavaScript to run.
So let'southward take a await at an example:
setTimeout(() => panel.log("how-do-you-do"), 0) fetch("https://someapi/information").then(response => response.json()) .then(data => panel.log(data)) console.log("What soup?")
What will the order be here?
- Firstly, setTimeout is delegated to the browser, which does the work and puts the resulting function in the macrotask queue.
- Secondly fetch is delegated to the browser, which takes the work. It retrieves the data from the endpoint and puts the resulting functions in the microtask queue.
- Javascript logs out "What soup"?
- The event loop checks whether or not JavaScript is set up to receive the results from the queued piece of work.
- When the console.log is done, JavaScript is gear up. The effect loop picks queued functions from the microtask queue, which has a higher priority, and gives them back to JavaScript to execute.
- Later the microtask queue is empty, the setTimeout callback is taken out of the macrotask queue and given back to JavaScript to execute.
In console: // What soup? // the data from the api // howdy
Promises
Now y'all should have a good bargain of knowledge virtually how asynchronous code is handled by JavaScript and the browser surround. Then let's talk nigh promises.
A promise is a JavaScript construct that represents a future unknown value. Conceptually, a promise is just JavaScript promising to return a value. Information technology could be the consequence from an API phone call, or it could exist an error object from a failed network request. You're guaranteed to go something.
const promise = new Promise((resolve, decline) => { // Brand a network asking if (response.status === 200) { resolve(response.body) } else { const fault = { ... } reject(fault) } }) promise.then(res => { console.log(res) }).take hold of(err => { panel.log(err) })
A promise can have the following states:
- fulfilled - activeness successfully completed
- rejected - action failed
- pending - neither action has been completed
- settled - has been fulfilled or rejected
A promise receives a resolve and a refuse function that can exist called to trigger one of these states.
Ane of the big selling points of promises is that we can concatenation functions that we want to happen on success (resolve) or failure (reject):
- To register a role to run on success we use .then
- To register a function to run on failure we utilise .catch
// Fetch returns a promise fetch("https://swapi.dev/api/people/ane") .and so((res) => panel.log("This function is run when the asking succeeds", res) .catch(err => console.log("This function is run when the request fails", err) // Chaining multiple functions fetch("https://swapi.dev/api/people/1") .then((res) => doSomethingWithResult(res)) .then((finalResult) => console.log(finalResult)) .take hold of((err => doSomethingWithErr(err))
Perfect. Now let's take a closer expect at what this looks like under the hood, using fetch as an case:
const fetch = (url, options) => { // simplified return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest() // ... make request xhr.onload = () => { const options = { status: xhr.condition, statusText: xhr.statusText ... } resolve(new Response(xhr.response, options)) } xhr.onerror = () => { reject(new TypeError("Request failed")) } } fetch("https://swapi.dev/api/people/i") // Register handleResponse to run when hope resolves .and then(handleResponse) .grab(handleError) // conceptually, the hope looks similar this now: // { condition: "awaiting", onsuccess: [handleResponse], onfailure: [handleError] } const handleResponse = (response) => { // handleResponse will automatically receive the response, ¨ // considering the hope resolves with a value and automatically injects into the role panel.log(response) } const handleError = (response) => { // handleError will automatically receive the error, ¨ // because the promise resolves with a value and automatically injects into the function panel.log(response) } // the promise volition either resolve or refuse causing it to run all of the registered functions in the respective arrays // injecting the value. Let's inspect the happy path: // one. XHR event listener fires // 2. If the request was successfull, the onload outcome listener triggers // 3. The onload fires the resolve(VALUE) function with given value // 4. Resolve triggers and schedules the functions registered with .then
So nosotros can use promises to do asynchronous work, and to exist sure that we can handle any result from those promises. That is the value proposition. If you want to know more most promises you lot can read more about them here and here.
When we use promises, we chain our functions onto the promise to handle the different scenarios.
This works, just we still demand to handle our logic inside callbacks (nested functions) once we become our results dorsum. What if nosotros could use promises but write synchronous looking code? It turns out we can.
Async/Await
Async/Await is a fashion of writing promises that allows us to write asynchronous code in a synchronous way. Allow'southward accept a look.
const getData = async () => { const response = wait fetch("https://jsonplaceholder.typicode.com/todos/1") const data = await response.json() console.log(data) } getData()
Nothing has inverse under the hood hither. We are all the same using promises to fetch information, merely now it looks synchronous, and we no longer accept .then and .take hold of blocks.
Async / Wait is actually just syntactic saccharide providing a style to create lawmaking that is easier to reason near, without irresolute the underlying dynamic.
Permit's take a look at how information technology works.
Async/Await lets us employ generators to pause the execution of a function. When nosotros are using async / await we are not blocking considering the function is yielding the control back over to the primary program.
And then when the promise resolves we are using the generator to yield control back to the asynchronous function with the value from the resolved promise.
You can read more here for a great overview of generators and asynchronous code.
In effect, we tin can now write asynchronous code that looks like synchronous lawmaking. Which means that it is easier to reason about, and nosotros tin use synchronous tools for fault treatment such as try / catch:
const getData = async () => { effort { const response = expect fetch("https://jsonplaceholder.typicode.com/todos/1") const data = await response.json() console.log(data) } catch (err) { console.log(err) } } getData()
Alright. So how do nosotros employ information technology? In society to utilize async / await we need to prepend the function with async. This does not make it an asynchronous function, it merely allows us to use await inside of it.
Declining to provide the async keyword volition event in a syntax mistake when trying to utilize await inside a regular function.
const getData = async () => { console.log("Nosotros can use await in this function") }
Because of this, we tin not utilise async / look on top level code. But async and await are still only syntactic sugar over promises. So nosotros can handle top level cases with promise chaining:
async office getData() { let response = await fetch('http://apiurl.com'); } // getData is a promise getData().then(res => panel.log(res)).catch(err => console.log(err);
This exposes another interesting fact virtually async / await. When defining a function as async, information technology will always render a hope.
Using async / await can seem like magic at first. But like whatsoever magic, it'due south just sufficiently advanced technology that has evolved over the years. Hopefully now y'all have a solid grasp of the fundamentals, and can use async / expect with confidence.
Conclusion
If you fabricated it here, congrats. You lot just added a cardinal slice of knowledge near JavaScript and how it works with its environments to your toolbox.
This is definitely a disruptive subject area, and the lines are not always clear. But now you hopefully have a grasp on how JavaScript works with asynchronous code in the browser, and a stronger grasp over both promises and async / expect.
If you enjoyed this article, you might also enjoy my youtube channel. I currently take a spider web fundamentals series going where I go through HTTP, building web servers from scratch and more.
There's also a series going on building an entire app with React, if that is your jam. And I plan to add together much more content here in the future going in depth on JavaScript topics.
And if you want to say hi or chat about spider web evolution, you could e'er reach out to me on twitter at @foseberg. Thanks for reading!
Acquire to code for gratuitous. freeCodeCamp's open source curriculum has helped more than 40,000 people get jobs as developers. Become started
Source: https://www.freecodecamp.org/news/async-await-javascript-tutorial/
0 Response to "Javascript Event Listener Wait for Function Before Firing Event Again"
Postar um comentário