Asynchronous JavaScript
📚 Table of Contents |
---|
Intro |
Promises |
Async/Await |
asynch JS intro
- asynchronous, multi-threaded computational programming sounds kind of intense, doesn't it?
- don't let the verbiage intimidate you, it basically just means that you are telling the computer to do multiple things at once
- in fact, we already sort of touched on this in the very first JavaScript section when we saw how to connect a JS script to an HTML file, when we used the defer attribute to allow the entire HTML document to load without waiting to load the JS script
- thats pretty much all this is except this is how you do that in the actual JS script
- we have also seen that computers will read code left to right and top to bottom by default, that core top to bottom (or synchronous) code is known as the
main thread
. when you have code that will allow the main thread to keep going but are doing things on the side (or in parallel to the main thread) or have code that is waiting to finish, you are creatingmultiple threads
- why is this necessary? some things just take more time than others. if all your code was synchronous, you could have a laggy, stuttering some sometimes a straight up broken UI because of a blocked thread
- some of these things that can take time are just diesel algorithms with complex computations or reading data from files, fetching data from a server (or backend) or communicating with an API
side note: 🤓 technically, JavaScript is inherently single-threaded but uses asynchronous callbacks and the event loop to effectively handle multiple operations, giving the illusion of multi-threading. JavaScript simulates concurrency. but who really cares?
promises
-
so a
promise
in JS is an object that represents the eventual completion (or failure) of an asynch function and the resulting value -
makes sense right, if you have code that's going to do something that might take an extra few seconds, the thing you are waiting for is "promised" to come back 🫠
-
promises
exist because without them, you end up with crazy nested functions in functions calling other functions with something affectionately called "the classic callback pyramid of doom". a) pyramid of doom is a sick band name b) reminder callbacks are functions being used as arguments for other functions -
promises
have three states:- pending: the operation has not completed yet, or its "still working"
- fulfilled: everything has completed successfully
- rejected: something failed
-
create a
promise
like this:
const myPromise = new Promise((resolve, reject) => {
// asynchronous operation goes here
if (/* operation is successful */) {
resolve('Success!');
} else {
reject('Failure.');
}
});
-
above is promise producing code. while there are scenarios where you can do that, its much more common (in both vanilla JS & React) to just consume promises
-
then
is the magic keyword to identifying and writing promise consuming code -
.then()
is how you chain promises together and use your results from the promise, it actually returns a new promise -
.then()
handles the results of the promise, meaning the promise was successful. if the promise fails, you gracefully handle that withcatch()
. remember try/catch? -
this is how it looks:
doSomething()
.then((result) => doSomethingElse(result))
.catch(failureCallback);
- you can chain
.then()
's together if you want or need to:
doSomething()
.then((result) => doSomethingElse(result))
.then((newResult) => doThirdThing(newResult))
.then((finalResult) => {
console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);
- you don't have to use arrow functions for promises. this is kind of rare to see these days but just in case you see it in the wild:
doSomething()
.then(function (result) {
return doSomethingElse(result);
})
.then(function (newResult) {
return doThirdThing(newResult);
})
.then(function (finalResult) {
console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);
- and a lot like the classic
try/catch
, you can actually usefinally
to wrap everything up after the promise is settled
doSomething()
.then((result) =>
console.log(`check out the ${result} from my successful promise`)
)
.catch((error) =>
console.log(`something went wrong, heres your error: ${error}`)
)
.finally(() => console.log("this code runs no matter what"));
async / await
async / await
is a cleaner way to consume promises- instead of chaining promises together with
.then()
, you make the entire function asynchronous and await the function that is responsible for the results - you use the keyword
async
to make a function asynchronous (🤓 technically this is whats known as "syntactic sugar" because again, we are only simulating concurrency) - putting
async
in front of the function declaration means that the function will return a promise await
makes a function wait for a promise- you use them together like this:
async function fetchData() {
const data = await someAsyncOperation();
console.log(data);
}
// or as an arrow function
const fetchData = async () => {
const data = await someAsyncOperation();
console.log(data);
};
await
is used insideasync
functions because it pauses the execution and waits for the promise- not gonna lie,
await
can be easy to forget and can definitely be an annoying bug to find so try to think of them together liketry / catch
andasync / await
. in fact, you will often useasync / await
with atry / catch
for error handling:
const fetchData = async () => {
try {
const data = await someAsyncOperation();
console.log(data);
} catch (error) {
console.error("Error:", error);
}
};