- ๐ [[Asynchronous#|Parallel vs Async]]
- ๐ [[Asynchronous#|Callbacks]]
- ๐ [[Asynchronous#|Thunks]]
- ๐ XMLHttpRequest
- ๐ Promises
- ๐ Fetch
- ๐ Await
- ๐ [[Asynchronous#|Generators/Coroutines]]
- ๐ [[Asynchronous#|Event Reactive (observables)]]
- ๐ [[Asynchronous#|CSP (channel-oriented concurrency)]]
Parallel vs Async
Parallelism
Parallelism is a concept in computing where multiple tasks or processes are executed simultaneously, potentially taking advantage of multiple processors or cores in a computer system. Itโs a way to improve the performance and efficiency of a system by breaking down a problem into smaller tasks that can be executed concurrently
Concurrency vs. Parallelism:
Concurrency and parallelism are related but different concepts.
- Concurrency is about managing multiple tasks and making progress on them, even if they are not executed simultaneously.
- Parallelism, on the other hand, involves executing multiple tasks simultaneously to improve overall performance.
Asynchronous
Asynchronous execution in JavaScript is a fundamental concept. It allows you to perform tasks without blocking the main thread. This is crucial for non-blocking I/O operations like fetching data from an API, reading a file, or making network requests. Asynchronous code often involves callbacks, Promises, or the more modern async/await
syntax.
Callback
What Are Callbacks?
In JavaScript, a callback is a function that is passed as an argument to another function. The primary purpose of a callback is to be executed later, after the completion of a particular operation or task. Callbacks are commonly used for asynchronous operations, such as handling events, making HTTP requests, or reading files.
Asynchronous Operations and Callbacks:
Callbacks are often associated with asynchronous operations, where a task takes some time to complete, and you donโt want to block the main thread of execution. Instead of waiting for the operation to finish, you pass a callback function to handle the result when itโs ready.
Callback Hell
Most people think of the call back hell has something to do with the indentation and the nesting but really it's not
Cause we can rewrite the program using continuation passing style (CPS) and still have a callback hell
Major Issue with call back
Callbacks can lead to callback hell, also known as the "pyramid of doom," when multiple asynchronous operations are nested inside one another. This can make code hard to read and maintain. To address this issue, modern JavaScript introduced Promises and async/await, which provide more structured and readable ways to handle asynchronous code.
Inversion of Control (IoC):
IoC refers to the practice of shifting control over certain aspects of a program from the program itself to an external component or framework. In other words, it involves allowing an external entity to control or manage parts of a programโs behavior.
Callbacks:
Callbacks are a mechanism for handling asynchronous operations in JavaScript. They allow you to specify a function (callback) to be executed once an operation is complete. In terms of IoC, callbacks can be seen as a form of Inversion of Control because the execution of the callback is controlled by the asynchronous operation or an external event. The main program hands over control to the callback function.
Promises:
Promises provide a more structured way to work with asynchronous operations. They encapsulate the result or error of an asynchronous task and allow you to attach .then() and .catch() handlers to specify what should happen when the task is complete or encounters an error. we are in a complete control of that paradigm. we have brought many sanity to this control.
With promises, we simply uninvert the inversion of control problem, promises were designed to retain the control we usually have in our code.
Thunks
A thunk is a function that encapsulates an action or a computation that can be deferred or delayed.
In the context of Redux and state management in JavaScript, a thunk is often used to represent asynchronous actions as functions. These functions return another function that can dispatch actions or perform asynchronous operations.
Thunks are typically used in conjunction with middleware like Redux Thunk to manage asynchronous side effects in a Redux application.
XMLHttpRequest
XMLHttpRequest is an API that allows you to make HTTP requests from a web page or a web application. It has been a fundamental part of web development for many years,
It is recommended to use newer APIs like the Fetch API are now recommended for making HTTP requests.
Creation of XHR Object:
Creating our XMLHTTP Instance
To make an HTTP request using XMLHttpRequest, you first need to create an instance of the XMLHttpRequest object.
Configuring the Request:
You configure the request by specifying the HTTP method (GET, POST, etc.) and the URL you want to request. You can also set request headers if needed.
The third parameter (
true
) specifies whether the request should be asynchronous (true
) or synchronous (false
). Itโs highly recommended to use asynchronous requests (true
) to prevent blocking the main thread.
Configuring Headers
Yup you can set one or multiple headers as needed using the setRequestHeader
Sending the Request:
To initiate the request, you use the send() method. If youโre making a POST request and need to send data, you can pass it as an argument to send().
Handling Responses: (onload on errors)
You set up event listeners to handle the response when it arrives. Common events include load
, error
, and progress
.
The onload event is triggered when the request completes successfully (HTTP status code 200). The onerror event is triggered for network errors or HTTP errors.
Synchronous vs. Asynchronous:
- ๐ฅ XHR requests can be made in either synchronous or asynchronous mode:
- โก๏ธ Synchronous (Blocking):
- ๐ Setting the third parameter of
xhr.open()
tofalse
makes the request synchronous. This means that the JavaScript code execution is blocked until the request completes. This approach is strongly discouraged because it can freeze the user interface and negatively affect the user experience.
- ๐ Setting the third parameter of
- โก๏ธ Asynchronous (Recommended):
- ๐ Setting the third parameter to
true
(or omitting it, as it defaults totrue
) makes the request asynchronous. In this mode, the request is sent to the server without blocking the rest of your code. You provide callback functions to handle the response when it arrives.
- ๐ Setting the third parameter to
- โก๏ธ Synchronous (Blocking):
Making a Simple GET Request:
ON READY STATE CHANGE EVENT LISTENER
Sometimes people attach an onReadyStateChange event listener to the xhr object and it would give somethin like this
In this code, we check if
xhr.readyState
is equal to 4, which indicates that the request is complete. Ifxhr.status
is 200, we handle a successful response; otherwise, we handle HTTP errors.This approach allows you to handle different stages of the request, such as when the request is in progress (
readyState
1, 2, 3) and when it's complete (readyState
4). It gives you more fine-grained control over handling the response and errors.
Making a simple Post Request
Creating a Promise based version of XMLHttpRequest
Promises
Promises are objects that represents a time independent eventual completion or failure of an asynchronous operation. They have three states: pending, resolved (fulfilled), and rejected.
Introducing the readability enhancer - Promises
Overview
What is it ?
In JavaScript, a Promise is a built-in object that represents a placeholder for a value that may not be available yet but will be resolved at some point in the future. It is used for handling asynchronous operations in a more elegant and organized way, making it easier to write and manage asynchronous code.
Promises were later standardized in ECMAScript 6 (ES6) through the Promises/A+ specification, making them a part of the core JavaScript language. They have since become a fundamental feature of modern JavaScript development, and their ease of use and clean syntax have significantly improved the way developers handle asynchronous operations.
How do they work ?
Special 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 automatically 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
Promises in JavaScript work under the hood by managing the state of asynchronous operations and providing a structured way to handle their results or errors. Promises are implemented as a combination of JavaScript code and the event loop, which is the core of JavaScriptโs concurrency model.
Why were they made in the first place ?
Promise and trust notion.
Another way of thinking bout Promises is that it's simply a callback manager, it is a pattern for managing our callbacks in a trustable manner.
- ๐ฅ Only Resolved once
- ๐ฅ Either success or error
- ๐ฅ Messages passed/kept
- ๐ฅ Exceptions become errors
- ๐ฅ Immutable once resolved
The advantages of promises.
Fixing the callback hell
Callback hell occurs when multiple asynchronous operations are nested deeply inside one another, leading to a complex and hard-to-maintain code structure. Promises provide a more structured and readable way to handle asynchronous code, reducing the likelihood of callback hell.
Error handling
In callback-based asynchronous code, error handling can become challenging because each callback must include error handling logic. Promises centralize error handling with the .catch() method, making it easier to handle and propagate errors consistently throughout the code.
Guaranteed Resolution or Rejection:
Promises ensure that an asynchronous operation will either resolve (succeed) or reject (fail) once and only once. This guarantee is important for reliability and consistency in asynchronous code. Callbacks can be called multiple times, which can lead to unexpected behavior.
Readability and Maintainability:
Promises improve code readability by allowing you to chain .then() methods in a sequential and organized manner, making it clear how asynchronous operations are coordinated.
Composition:
Promises support composition, where you can combine multiple asynchronous operations into a single flow of control. This is valuable for orchestrating complex asynchronous tasks.
Avoiding Callback Pyramid:
Promises help avoid the callback pyramid structure by providing a more linear and visually organized way to express asynchronous code.
Better Debugging:
Promises offer better debugging capabilities, as you can set breakpoints and inspect the flow of control more easily than with deeply nested callbacks.
Eventual Consistency:
Promises help ensure that code relying on asynchronous operations remains consistent and predictable, reducing race conditions and timing issues.
Thenables ar Promises
The JavaScript ecosystem had made multiple Promise implementations long before it became part of the language. Despite being represented differently internally, at the minimum, all Promise-like objects implement the Thenable interface. A thenable implements the .then()
method, which is called with two callbacks: one for when the promise is fulfilled, one for when itโs rejected. Promises are thenables as well.
A "thenable" is an object that has a
.then()
method. It doesn't have to be a Promise itself, but it behaves like one in the sense that you can attach.then()
callbacks to it.
In this case,
thenable
is not a Promise, but it has a.then()
method, allowing us to use.then()
to handle its asynchronous operation.So, a โthenableโ is any object that conforms to the expected behavior of having a
.then()
method, which allows it to be used in Promise-like asynchronous workflows. Understanding โthenablesโ can be useful when working with Promises and related asynchronous patterns in JavaScript.
Control flow
Promises are objects that represent the eventual completion or failure of an asynchronous operation. They have three states: pending, resolved (fulfilled), and rejected. Promises are used to manage the flow of asynchronous code, making it more organized and readable.
Creating a promise instance
To Create a promise you simply create an instance from the Promise class and pass it your executor function.
Yes, the executor function is a must and yes it gets executed as soon as you create your promise.
then(), finally(), catch()
Promise.prototype.then()
Syntax
Usage
The then()
method of Promise instances takes up to two arguments: callback functions for the fulfilled
and rejected cases of the Promise
. It immediately returns an equivalent Promise object, allowing you to chain calls to other promise methods.
Having a non-function as either parameter
Promise.prototype.catch()
The catch()
method of Promise instances schedules a function to be called when the promise is rejected. It immediately returns an equivalent Promise object, allowing you to chain calls to other promise methods. It is a shortcut for Promise.prototype.then(undefined, onRejected).
If a promise becomes rejected, and there are no rejection handlers to call (a handler can be attached through any of then(), catch(), or finally()), then the rejection event is surfaced by the host. In the browser, this results in an
unhandledrejection
event.
โ catch() internally calls then() on the object upon which it was called, passing undefined and onRejected as arguments. The value of that call is directly returned. This is observable if you wrap the methods.๐
Gothas Throwing Errors.
Throwing an error will call the
catch()
method most of the time:
Errors thrown inside asynchronous functions will act like uncaught errors:
Errors thrown after
resolve
is called will be silenced:
Catch is never called if the promise is fulfilled.
Promise.prototype.finally()
The finally() method of Promise instances schedules a function to be called when the promise is settled (either fulfilled or rejected). It immediately returns an equivalent Promise object, allowing you to chain calls to other promise methods.
This lets you avoid duplicating code in both the promiseโs
then()
andcatch()
handlers.
The
finally()
method is very similar to callingthen(onFinally, onFinally)
. However, there are a couple of differences:
Example using finaly
Chaining Promises for Sequential Execution
Promises can be chained using .then()
to ensure sequential execution of asynchronous tasks. Each .then()
block executes only when the previous one resolves.
Parallel Execution with Promise.all()
Promise.all() allows you to execute multiple asynchronous tasks concurrently and wait for all of them to complete before proceeding.
Error Handling with Promises
Promises provide consistent error handling using .catch(). You can place a single .catch() handler at the end of a chain to handle any errors that occur in any step.
Conditional Control Flow
Use conditional statements within your promise chain to control whether specific tasks should execute based on certain conditions.
Returning Promises in .then()
Each .then()
can return a new Promise, enabling complex control flow scenarios like branching or dynamically generating promises based on previous results.
Using promises.
Promises provide a more structured way of handling asynchronous operations.
A Promise is an object representing the eventual completion or failure of an asynchronous operation. It has methods like then()
and catch()
to handle success and error cases respectively.
Creating a promise
A Promise is an object representing the eventual completion or failure of an asynchronous operation. Since most people are consumers of already-created promises
Creating a promise
Creating a promise that wrap normal piece of codes.
A promise version of a setTimeout
A promise an event listener
Here our event will only run once and wont run again
The reason your promise-based event listener is running only once is because the
resolve
function is invoked only once when the event occurs. After that, the promise is considered fulfilled, and subsequent invocations of the event will not trigger theresolve
function again.
Chaining promises
A common need is to execute two or more asynchronous operations back to back, where each subsequent operation starts when the previous operation succeeds, with the result from the previous step. In the old days, doing several asynchronous operations in a row would lead to the classic callback pyramid of doom:
With promises, we accomplish this by creating a promise chain. The API design of promises makes this great, because callbacks are attached to the returned promise object, instead of being passed into a function.
Chaining Promises (Optional) Promises can be chained together to perform a series of asynchronous operations in a sequential manner. You can use the
then()
method to chain multiple promises.
By chaining promises, you can ensure that each asynchronous operation is executed one after the other, and handle any errors that occur along the way.
With this pattern, you can create longer chains of processing, where each promise represents the completion of one asynchronous step in the chain. In addition, the arguments to then are optional, and catch(failureCallback) is short for then(null, failureCallback) โ so if your error handling code is the same for all steps, you can attach it to the end of the chain:
Always Return Results
Warning Important: Always return results, otherwise callbacks won't catch the result of a previous promise
This may be worse if you have race conditions: if the promise from the last handler is not returned, the next then handler will be called early, and any value it reads may be incomplete.
Therefore, as a rule of thumb, whenever your operation encounters a promise, return it and defer its handling to the next
then
handler.
Nesting
In the two examples above, the first one has one promise chain nested in the return value of another then() handler, while the second one uses an entirely flat chain. Simple promise chains are best kept flat without nesting, as nesting can be a result of careless composition. common mistakes.
Nesting is a control structure to limit the scope of catch
statements. Specifically, a nested catch
only catches failures in its scope and below, not errors higher up in the chain outside the nested scope. When used correctly, this gives greater precision in error recovery:
The inner error-silencing
catch
handler only catches failures fromdoSomethingOptional()
anddoSomethingExtraNice()
, after which the code resumes withmoreCriticalStuff()
. Importantly, ifdoSomethingCritical()
fails, its error is caught by the final (outer)catch
only, and does not get swallowed by the innercatch
handler.
Chaining after a catch
It's possible to chain after a failure, i.e. a catch, which is useful to accomplish new actions even after an action failed in the chain.
The text "Do this" is not displayed because the "Something failed" error caused a rejection.
Common Mistakes
There are some common mistakes to watch out for when composing promise chains. Several of these mistakes manifest in the following example:
Return in a Chain, Unnecessary Nesting & Forgetting a catch โ
The first mistake is to not chain things together properly. This happens when we create a new promise but forget to return it. As a consequence, the chain is broken โ or rather, we have two independent chains racing. This means
doFourthThing()
won't wait fordoSomethingElse()
ordoThirdThing()
to finish, and will run concurrently with them โ which is likely unintended. Separate chains also have separate error handling, leading to uncaught errors.
The second mistake is to nest unnecessarily, enabling the first mistake. Nesting also limits the scope of inner error handlers, which if unintended can lead to uncaught errors.
The third mistake is forgetting to terminate chains with
catch
. Unterminated promise chains lead to uncaught promise rejections in most browsers
A good rule of thumb is to always either return or terminate promise chains, and as soon as you get a new promise, return it immediately, to flatten things:
## Now we have a single deterministic chain with proper error handling.
Error Handling
For three chains you might surely see three error handling in the pyramid of doom callback style of coding compared to only one in a promise
If there's an exception, the browser will look down the chain for
.catch()
handlers oronRejected
.This is very much modeled after how synchronous code works:
Promises solve a fundamental flaw with the callback pyramid of doom, by catching all errors, even thrown exceptions and programming errors. This is essential for functional composition of asynchronous operations.
Composition / Promise Concurrency
The Promise class offers four static methods to facilitate async task concurrency:
There are four composition tools for running asynchronous operations concurrently: Promise.all()
, Promise.allSettled()
, Promise.any()
, and Promise.race()
.
- ๐ก
Promise.all()
- โก๏ธ Fulfills when all of the promises fulfill; rejects when any of the promises rejects.
- ๐ก
Promise.allSettled()
- โก๏ธ Fulfills when all promises settle.
- ๐ก
Promise.any()
- โก๏ธ Fulfills when any of the promises fulfills; rejects when all of the promises reject.
- ๐ก
Promise.race()
- โก๏ธ Settles when any of the promises settles. In other words, fulfills when any of the promises fulfills; rejects when any of the promises rejects.
Promise.all()
The Promise.all() static method takes an iterable of promises as input and returns a single Promise. This returned promise fulfills when all of the inputโs promises fulfill (including when an empty iterable is passed), with an array of the fulfillment values. It rejects when any of the inputโs promises rejects, with this first rejection reason.
Syntax
Description
The Promise.all()
method is one of the promise concurrency methods. It can be useful for aggregating the results of multiple promises.
It is typically used when there are multiple related asynchronous tasks that the overall code relies on to work successfully all of whom we want to fulfill before the code execution continues.
Info
Promise.all()
will reject immediately upon any of the input promises rejecting. In comparison, the promise returned byPromise.allSettled()
will wait for all input promises to complete, regardless of whether or not one rejects. UseallSettled()
if you need the final result of every promise in the input iterable.
Using promise.all()
Promise.all
waits for all fulfillments (or the first rejection).
Info
Promise.all
resolves synchronously if and only if theiterable
passed is empty:
Promise.allSettled()
The Promise.allSettled() static method takes an iterable of promises as input and returns a single Promise. This returned promise fulfills when all of the inputโs promises settle (including when an empty iterable is passed), with an array of objects that describe the outcome of each promise.
Syntax
Return value
A Promise that is:
-
Already fulfilled, if the iterable passed is empty.
-
Asynchronously fulfilled, when all promises in the given iterable have settled (either fulfilled or rejected). The fulfillment value is an array of objects, each describing the outcome of one promise in the iterable, in the order of the promises passed, regardless of completion order. Each outcome object has the following properties:
-
โ status
- A string, either โfulfilledโ or โrejectedโ, indicating the eventual state of the promise.
-
โ value
- Only present if status is โfulfilledโ. The value that the promise was fulfilled with.
-
โ reason
- Only present if status is โrejectedโ. The reason that the promise was rejected with.
If the iterable passed is non-empty but contains no pending promises, the returned promise is still asynchronously (instead of synchronously) fulfilled.
Example
Promise.race()
Gotchas
-
Fastest Wins:
- Gotcha:
Promise.race
resolves or rejects as soon as the first Promise in the input array settles (either fulfills or rejects). This means that it doesnโt wait for all Promises to complete, only the first one. - Understanding: Use
Promise.race
when you want to act on the result of the first settled Promise, such as implementing a timeout mechanism.
- Gotcha:
-
No Cancellation:
- Gotcha: Unlike some other asynchronous patterns,
Promise.race
does not inherently support cancellation of the remaining Promises once one has settled. - Understanding: If you need to cancel other Promises in the race after one settles, youโll need to implement custom cancellation logic, which can be complex.
- Gotcha: Unlike some other asynchronous patterns,
-
Race Conditions:
- Gotcha: When using
Promise.race
, be aware of potential race conditions. If multiple Promises resolve or reject at nearly the same time, it can lead to unpredictable behavior. - Understanding: Ensure that the Promises youโre racing against are designed to handle such scenarios gracefully.
- Gotcha: When using
-
Timeouts:
- Gotcha:
Promise.race
is commonly used for implementing timeouts. However, if the timeout Promise resolves first, it wonโt automatically cancel the other Promises; they will continue running. - Understanding: Implement your own logic to handle the cancellation of other Promises when a timeout occurs if necessary.
- Gotcha:
-
Single Winner:
- Gotcha:
Promise.race
returns the result (fulfillment value or rejection reason) of the first settled Promise. If you need to capture and handle multiple outcomes, consider alternatives likePromise.all
.
- Gotcha:
-
Error Handling:
- Gotcha: Ensure that you handle errors effectively within each Promise in the race. A rejection of any Promise will cause
Promise.race
to reject immediately, so you should handle errors within each Promise to prevent unhandled promise rejections. - Understanding: Include error handling (
.catch()
) within each Promise or catch errors after thePromise.race
operation to avoid unhandled rejections.
- Gotcha: Ensure that you handle errors effectively within each Promise in the race. A rejection of any Promise will cause
Promise.any()
-
Fulfillment Only:
- Gotcha:
Promise.any
resolves as soon as the first Promise in the input array fulfills. UnlikePromise.race
, it does not consider rejections as a signal to resolve. - Understanding: Use
Promise.any
when you specifically want to capture the fulfillment value of the first resolved Promise, and youโre not concerned about rejections.
- Gotcha:
-
Handling Rejections:
- Gotcha: If all Promises in the input array reject,
Promise.any
will reject with anAggregateError
containing all the rejection reasons. It wonโt resolve with a fulfillment value. - Understanding: Be prepared to handle the
AggregateError
that may contain multiple rejection reasons. You can use methods like.catch()
orPromise.allSettled
to handle rejections gracefully.
- Gotcha: If all Promises in the input array reject,
-
No Cancellation:
- Gotcha: Like
Promise.race
,Promise.any
does not inherently support cancellation of the remaining Promises once one has fulfilled. - Understanding: If you need to cancel other Promises in the race after one fulfills, youโll need to implement custom cancellation logic.
- Gotcha: Like
-
Multiple Winners:
- Gotcha: If multiple Promises in the input array fulfill simultaneously (e.g., with the same microtask), the behavior may be unpredictable, as
Promise.any
resolves with the first fulfilled Promise. - Understanding: Ensure that the Promises youโre using with
Promise.any
do not produce simultaneous fulfillments to maintain predictable behavior.
- Gotcha: If multiple Promises in the input array fulfill simultaneously (e.g., with the same microtask), the behavior may be unpredictable, as
-
Error Handling:
- Gotcha: While
Promise.any
is designed to capture the fulfillment value of the first resolved Promise, itโs still important to handle errors within each Promise in the input array to prevent unhandled promise rejections. - Understanding: Include error handling (
.catch()
) within each Promise or catch errors after thePromise.any
operation to avoid unhandled rejections.
- Gotcha: While
Fetch
The Fetch API provides a JavaScript interface for accessing and manipulating parts of the protocol, such as requests and responses. It also provides a global fetch() method that provides an easy, logical way to fetch resources asynchronously across the network.
Unlike
XMLHttpRequest
that is a callback-based API, Fetch is promise-based and provides a better alternative that can be easily used in service workers. Fetch also integrates advanced HTTP concepts such as CORS and other extensions to HTTP.
fetch()
allows you to make network requests similar to XMLHttpRequest (XHR). The main difference is that the Fetch API uses Promises, which enables a simpler and cleaner API, avoiding callback hell and having to remember the complex API of XMLHttpRequest.
XMLHttpRequest vs Fetch
Letโs start by comparing a simple example implemented with an XMLHttpRequest
and then with fetch
. We just want to request a URL, get a response and parse it as JSON
XMLHttpRequest
An XMLHttpRequest
would need two listeners to be set to handle the success and error cases and a call to open()
and send()
Fetch
Our fetch request looks a little like this:
We start by checking that the response status is 200 before parsing the response as JSON.
The response of a
fetch()
request is a Stream object, which means that when we call thejson()
method, a Promise is returned since the reading of the stream will happen asynchronously.
Fetch
Paramerters
resource
- โก๏ธ This defines the resource that you wish to fetch. This can either be:
- ๐ A string or any other object with a stringifier โ including a URL object โ that provides the URL of the resource you want to fetch.
- ๐ A Request object.
Options
An object containing any custom settings that you want to apply to the request. The possible options are:
method
The request method, e.g., "GET"
, "POST"
. The default is "GET"
.
headers
body
mode
credentials
omit
same-origin
include
cahce
โฆ
Overview
- ๐ก The global fetch() method starts the process of fetching a resource from the network, returning a promise which is fulfilled once the response is available.
- ๐ก The promise resolves to the Response object representing the response to your request.
- ๐ฅ No rejection on a 404.
A
fetch()
promise only rejects when a network error is encountered (which is usually when there's a permissions issue or similar). Afetch()
promise does not reject on HTTP errors (404
, etc.). Instead, athen()
handler must check theResponse.ok
and/orResponse.status
properties.
This will return a promise that contains the response data. This response data contains properties for the status as well as methods for converting the raw response data to JSON, text, or other formats.
The highlighted code above is calling the
json
method on our response and it is returning that from the.then
function. This is because thejson
method also returns a promise that evaluates to the JSON data from our response. We can chain a second.then
to get the data from thejson
method.
This is what most of your fetch requests will look like if you are fetching data from a JSON api. We first fetch the URL, then we convert the response to JSON, and finally we use the data in the final
.then
.
Response Metadata
In the previous example we looked at the status of the Response object as well as how to parse the response as JSON. Other metadata we may want to access, like headers, are illustrated below.
Response Metadata
Response Types
When we make a fetch request, the response will be given a response.type
of โbasicโ, โcorsโ or โopaqueโ. These types indicate where the resource has come from and can be used to inform how you should treat the response object.
- Basic
- When a request is made for a resource on the same origin, the response will have a
basic
type and there arenโt any restrictions on what you can view from the response.
- When a request is made for a resource on the same origin, the response will have a
- Cors
- If a request is made for a resource on another origin which returns the CORs headers, then the type is cors.
- opaque
- response is for a request made for a resource on a different origin that doesnโt return CORS headers. With an opaque response we wonโt be able to read the data returned or view the status of the request, meaning we canโt check if the request was successful or not.
You can define a mode for a fetch request such that only certain requests will resolve. The modes you can set are as follows:
- ๐
same-origin
- โก๏ธ only succeeds for requests for assets on the same origin, all other requests will reject.
- ๐
cors
- โก๏ธ will allow requests for assets on the same-origin and other origins which return the appropriate CORs headers.
- ๐
cors-with-forced-preflight
- โก๏ธ will always perform a preflight check before making the actual request.
- ๐
no-cors
- โก๏ธ is intended to make requests to other origins that do not have CORS headers and result in an
opaque
response, but as stated, this isnโt possible in the window global scope at the moment.
- โก๏ธ is intended to make requests to other origins that do not have CORS headers and result in an
To define the mode, add an options object as the second parameter in the
fetch
request and define the mode in that object:
Chaining Promises
One of the great features of promises is the ability to chain them together. For fetch, this allows you to share logic across fetch requests.
If you are working with a JSON API, youโll need to check the status and parse the JSON for each response. You can simplify your code by defining the status and JSON parsing in separate functions which return promises, freeing you to only worry about handling the final data and the error case.
We define the status function which checks the response.status and returns the result of Promise.resolve() or Promise.reject(), which return a resolved or rejected Promise. This is the first method called in our fetch() chain, if it resolves, we then call our json() method which again returns a Promise from the response.json() call. After this we have an object of the parsed JSON. If the parsing fails the Promise is rejected and the catch statement executes.
The great thing with this is that you can share the logic across all of your fetch requests, making code easier to maintain, read and test.
POST Request
Itโs not uncommon for web apps to want to call an API with a POST method and supply some parameters in the body of the request.
To do this we can set the method
and body
parameters in the fetch()
options.
Sending Credentials with a Fetch Request
Should you want to make a fetch request with credentials such as cookies, you should set the credentials of the request to โincludeโ.
Async/Await
Info
async/await
is a modern JavaScript feature that simplifies asynchronous code and makes it more readable and maintainable. It is built on top of Promises and provides a way to write asynchronous code in a more synchronous style.
With
async/await
, you can write non-blocking code that appears sequential, making it easier to understand and debug asynchronous operations like network requests, file I/O, and timers.
Syntax
Info Note: There cannot be a line terminator between
async
andfunction
, otherwise a semicolon is automatically inserted, causingasync
to become an identifier and the rest to become afunction
declaration.
Parameters
- ๐ฅ name
- โก๏ธ The functionโs name.
- ๐ฅ param (Optional)
- โก๏ธ The name of a formal parameter for the function. For the parametersโ syntax, see the Functions reference.
- ๐ฅ statements (Optional)
- โก๏ธ The statements comprising the body of the function. The await mechanism may be used.
The async keyword is used to define an asynchronous function, which returns a Promise. Inside an async function, you can use the await keyword to pause the functionโs execution until a Promise is resolved. The await keyword can only be used inside an async function.
- โก๏ธ
fetchData
is anasync
function that fetches data from an API. - โก๏ธ
await fetch('https://api.example.com/data')
pauses the function until the HTTP request is complete. - โก๏ธ
await response.json()
pauses the function until the JSON parsing is complete. - โก๏ธ Errors are caught using a
try/catch
block.
Async functions always return a promise. If the return value of an async function is not explicitly a promise, it will be implicitly wrapped in a promise.
It is similar to:
The async function declaration creates a binding of a new async function to a given name. The await keyword is permitted within the function body, enabling asynchronous, promise-based behavior to be written in a cleaner style and avoiding the need to explicitly configure promise chains.
Usage
To call an async
function and handle its result, you can use .then()
and .catch()
or try/catch
:
Yes this is great compared to the callbacks but check bellow for the async way!
With Async Await you get an even clear syntax!
Parallel Execution
You can use Promise.all to run multiple asynchronous operations in parallel:
Sequential Execution
To execute asynchronous operations sequentially, use await
in a loop:
AbortController: abort() Method
The abort() method of the AbortController interface aborts a DOM request before it has completed.
This is able to abort fetch requests, the consumption of any response bodies, or streams.
Parameters
- โก๏ธ
reason
Optional- [/] The reason why the operation was aborted, which can be any JavaScript value. If not specified, the reason is set to โAbortErrorโ
DOMException
.
- [/] The reason why the operation was aborted, which can be any JavaScript value. If not specified, the reason is set to โAbortErrorโ
Return Value None (undefined)
Example
In the following snippet, we aim to download a video using the Fetch API.
We first create a controller using the AbortController()
constructor, then grab a reference to its associated AbortSignal
object using the AbortController.signal
property.
When the fetch request is initiated, we pass in the AbortSignal
as an option inside the requestโs options object (the {signal}
below). This associates the signal and controller with the fetch request and allows us to abort it by calling AbortController.abort()
, as seen below in the second event listener.
Note: When abort() is called, the fetch() promise rejects with an Error of type DOMException, with name AbortError.
Step By Step GUIDE
Info Overview To use the
AbortController
, create an instance of it, and pass itssignal
into a promise.
Inside the actual promise you listen for the
abort
event to be fired on the givensignal
, upon which the promise will be cancelled.
Tips and tricks
Temporal Dependency
Temporal dependency, also known as time-based dependency or time dependency, refers to the relationship between different events or processes in a system based on the sequence or timing of their occurrence. In other words, it describes how one event or process relies on the timing or order of another event or process.
Consider Promise.allSettled() Instead Promise.all()
These two methods behave differently, and itโs completely fine to use one or the other. But, before Promise.allSettled()
was introduced in ES2020, developers were desperately trying to โfixโ Promise.all()
behavior. Namely, Promise.all()
would reject upon any of the promises rejecting, with the return value of the first rejected promise. What if we still want to get the values of the resolved promises?
A workaround solution is to add a custom catch()
method to every promise in the input promise array. In the example below, the catch()
method will set the value of the associated promise to โrejectedโ in case the promise gets rejected.
With Promise.allSettled()
we get an array of objects with the outcome of each promise out of the box. It will never reject - instead, it will wait for all promises passed in the array to either resolve or reject.
Key takeaway tips: Use
Promise.all()
to request in parallel and fail fast upon rejection, without waiting for all promises to resolve. UsePromise.allSettled()
to request in parallel, never fail and get all the results.
Advanced Error Handling Async/Await & Promises
Advanced Error Handling: (NODE Environment)
You can use a global unhandledRejection event to handle unhandled promise rejections at the application level.
Global window
Object:
In a browser environment, you can use the global window
object to handle unhandled promise rejections. You can attach an event listener to the unhandledrejection
event like this:
This approach allows you to catch unhandled promise rejections at the browser level and provide custom error handling.
window.onerror:
You can also use the
window.onerror
event handler to catch unhandled promise rejections:
This event handler captures various types of errors, including unhandled promise rejections.