Editor’s note: This post was edited and updated in August 2021 to include new information.
If you’ve written asynchronous JavaScript code before, then you already have an idea about using callbacks and the issues with them. One major issue with using callbacks is the possibility of running into callback hell.
In ES2015, JavaScript promises were added to the language spec, bringing about an entirely new shift in how asynchronous code is written, and also mitigating the issue of running into callback hell. If you are using ES2015 syntax in your code, you may already be familiar with promises.
In this guide, you’ll learn some practical ways to improve asynchronous programming in JavaScript using promises, including:
.then()
handlers.then()
handlers returning promisesNote: This guide is not in any way an introduction to JavaScript promises. Some prior knowledge of promises is required to read this guide.
A JavaScript promise can be created using the Promise
constructor. The constructor takes an executor
function as its argument, which is immediately executed to create the promise.
The executor
, in turn, can take two callback functions as its arguments that can be invoked within the executor function in order to settle the promise, namely:
resolve
for fulfilling the promise with a valuereject
for rejecting the promise with a reason (usually an error)Here is a very simple JavaScript promise:
const isLessThan10 = (num) => { new Promise((resolve, reject) => { if (num < 10) { resolve("Correct"); } else { reject("Wrong!!"); } }) .then((res) => console.log(res)) .catch((err) => console.log(err)); }; isLessThan10(14);
If you run the above code, you will see “Wrong!!” in your console, meaning the promise got rejected
. That is because 14
is obviously not less than 10
, but when you pass a number lesser than 10
, the promise will be fulfilled
.
In the section above, you must have noticed our use of two words: rejected
and fulfilled
. These are two of the three states of a JavaScript promise. Let’s talk about the three possible states of a promise.
rejected
– a promise is rejected when the operation fails, e.g., above in the isLessThan10
function, when we passed 14, the promise was rejectedfulfilled
– a promise is fulfilled when the operation works or is correct, e.g., in the function above, passing a number less than 10 fulfills the promisePending
– a promise is pending when it is waiting to be resolved or rejected. A promise only gets to this state when then operation is asynchronousA promise is only fulfilled when it is resolved using a promise resolve argument. promise.resolve
fulfills a promise with a value, while a promise is rejected with the promise reject argument. These two states show the promise was settled and is no longer pending.
Often times, you just want to create a promise that is already settled — either fulfilled with a value, or rejected with a reason. For cases like this, the Promise.resolve()
and Promise.reject()
methods come in handy. Here is a simple example:
// This promise is already fulfilled with a number (100) const fulfilledPromise = Promise.resolve(100); // This promise is already rejected with an error const rejectedPromise = Promise.reject(new Error('Operation failed.')); // Getting the rsolved value of the promise fulfilledPromise.then(res => console.log(res)); // Getting catching to see the error of the promise rejectedPromise.then(res => console.log(res)).catch(err => console.log(err.message));
There could also be times when you are not sure if a value is a promise or not. In cases like this, you can use Promise.resolve()
to create a fulfilled promise with the value and then work with the returned promise. Here is an example:
// User object const USER = { name: 'Glad Chinda', country: 'Nigeria', job: 'Fullstack Engineer' }; // Create a fulfilled promise using Promise.resolve() Promise.resolve(USER) .then(user => console.log(user.name));
A settled promise can be handled by passing callbacks to the then()
, catch()
, or finally()
methods of the promise, as seen above in some earlier code snippets. Here, we will refactor the isLessThan10
function and see how to handle rejected and fulfilled promises.
const isLessThan10 = (num) => { return new Promise((resolve, reject) => { if (num < 10) { resolve("Correct"); } else { reject("Wrong!!!"); } }) }; // Case1 isLessThan10(1) .then(console.log) .catch(console.error); // Case2 // Alternatively, the rejection can be handled in the same .then() call // By passing the rejection handler as second argument to .then() isLessThan10(12).then(console.log, console.error);
Apart from using .catch()
to handle rejected promises, as seen above, we can also pass two callbacks to .then()
. The first will handle the promise if it is fulfilled, while the other will handle it if rejected. We can also manipulate the resolved value of the promise in the then()
block.
.finally()
is always executed once the promise is settled, regardless of whether it is fulfilled or rejected. It is a good place to perform cleanup actions like resetting a variable or clearing a state.
const isLessThan10 = (num) => { return new Promise((resolve, reject) => { if (num < 10) { resolve("Correct"); } else { reject("Wrong!!!"); } }) .then(111) .catch(222); }; isLessThan10(11) .then((res) => console.log(res)) .catch((err) => console.error(err)) .finally(() => console.log("This promise is finally settled!"));
then
handlersThe .then()
method can take up to two handler functions as its arguments: fulfillment handler and rejection handler.
However, if any of these two arguments are not a function, .then()
replaces that argument with a function and continues with the normal execution flow. It becomes important to know what kind of function the argument is replaced with. Here is what it is:
Here is a simple example:
const isLessThan10 = (num) => { return new Promise((resolve, reject) => { if (num < 10) { resolve("Correct"); } else { reject("Wrong!!!"); } }) .then(111) // Just a random number .catch(222); // Just a random number }; //This will log 'Correct' to the console isLessThan10(3).then(res => console.log(res)).catch(err => console.error(err)); // This will log 'Wrong' to the console isLessThan10(13).then(res => console.log(res)).catch(err => console.error(err));
If you observe carefully, you will notice that neither the identity
function nor the thrower
function alters the normal execution flow of the promise sequence. They simply have the same effect as omitting that particular .then()
call in the promise chain. For this reason, I usually refer to these handler arguments as “dumb handlers”.
.then()
handlers always return promisesOne important thing to understand about the .then()
promise method is that it always returns a promise.
Here is a breakdown of how .then()
returns a promise based on what is returned from the handler function passed to it:
Promises can be very useful for timing applications. Some programming languages like PHP have a sleep()
function that can be used to delay the execution of an operation until after the sleep time.
While a sleep()
function does not exist as part of the JavaScript spec, the global setTimeout()
and setInterval()
functions are commonly used for executing time-based operations.
The setInterval()
method is a JavaScript function used to execute a code block at specified time with delays between each call, while the setTimeout()
method is used to add a timer to a JavaScript code block.
Here is how the sleep()
function can be simulated using promises in JavaScript. However, in this version of the sleep()
function, the halt time will be in milliseconds instead of seconds:
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
Here is a slightly expanded and annotated version of the sleep(ms)
function:
const sleep = ms => { // Return a new promise // No need defining the executor function with a `reject` callback return new Promise(resolve => { // Pass resolve as the callback to setTimeout // This will execute `resolve()` after `ms` milliseconds setTimeout(resolve, ms); }); }
The sleep(ms)
function can can even be improved further to be come a self-contained delay
function that executes a callback
function after the specified sleep time.
Here is what using the sleep()
function could look like:
// Sleep for 5 seconds // Then execute the operation sleep(5000).then(executeOperation); // Delay function // Using async/await with sleep() const delay = async (callback, seconds = 1) => { // Sleep for the specified seconds // Then execute the operation await sleep(seconds * 1000); callback(); } // Using the `delay()` function // Execution delayed by 5 seconds delay(executeOperation, 5);
What if you are interested in knowing how long it took for an asynchronous operation to be completed? This is usually the case when benchmarking the performance of some form of implementation or functionality.
Here is a simple implementation that leverages a JavaScript promise to compute the execution time for an asynchronous operation.
const timing = callback => { // Get the start time using performance.now() const start = performance.now(); // Perform the asynchronous operation // Finally, log the time difference return Promise.resolve(callback()) .finally(() => console.log(`Timing: ${performance.now() - start}`)); }
In this implementation, performance.now()
is used instead of Date.now()
for getting the timestamp with a higher resolution. For non-browser environments where the performance
object does not exist, you can fallback on using Date.now()
or other host implementations.
In the code block below, the timing()
function could be used to log the execution time of an asynchronous operation on the console:
// Async operation that takes between 1 - 5 seconds const asyncOperation = () => new Promise(resolve => { setTimeout(() => resolve('DONE'), Math.ceil(Math.random() * 5) * 1000); }); // Compute execution time in ms // And log it to the console timing(asyncOperation); // Timing: 4003.4000000014203
With JavaScript promises, you can execute asynchronous operations in sequence. This is usually the case when a later asynchronous operation depends on the execution of a former asynchronous operation, or when the result of a former asynchronous operation is required for a later operation.
Executing asynchronous operations in sequence usually involves chaining one or more .``then()
and .catch()
handlers to a promise. When a promise is rejected in the chain, it is handled by the rejection handler defined in the next .then()
handler in the chain and then execution continues down the chain.
However, if no rejection handler has been defined in the next .then()
handler in the chain, the promise rejection is cascaded down the chain until it reaches the first .catch()
handler.
Let’s say you are building a photo gallery application, and you want to be able to fetch photos from an online photo repository, then filter them by format, aspect-ratio, dimension ranges, etc.
Here are some possible functions you could have in your application:
/** * Fetches photos from the Picsum API * @returns {Promise} A promise that is fulfilled with an array of photos from the Picsum repository */ const fetchPhotos = () => fetch('https://picsum.photos/list') .then(response => response.json()); /** * Filters photos and returns only JPEG photos * @param {Array} photos * @returns {Array} An array of JPEG photos */ const jpegOnly = photos => photos.filter(({ format }) => format.toLowerCase() === 'jpeg') /** * Filters photos and returns only square photos * @param {Array} photos * @returns {Array} An array of square photos */ const squareOnly = photos => photos.filter(({ width, height }) => height && Number.isFinite(height) && (width / height) === 1) /** * Returns a function for filtering photos by size based on `px` * @param {number} px The maximum allowed photo dimension in pixels * @returns {Function} Function that filters photos and returns an array of photos smaller than `px` */ const smallerThan = px => photos => photos.filter(({ width, height }) => Math.max(width, height) < px) /** * Return an object containing the photos count and URLs. * @param {Array} photos * @returns {Object} An object containing the photos count and URLs */ const listPhotos = photos => ({ count: photos.length, photos: photos.map(({ post_url }) => post_url) })
In the code block above, the fetchPhotos()
function fetches a collection of photos from the Picsum Photos API using the global fetch()
function provided by the Fetch API, and returns a promise that is fulfilled with a collection of photos.
Here is what the collection returned from the Picsum Photos API looks like:
The filter functions accept a collection of photos as an argument and filter the collection in some manner of the following ways:
jpegOnly()
 —  filters a photo collection and returns a sub-collection of only JPEG imagessquareOnly()
 —  filters a photo collection and returns a sub-collection of only photos with a square aspect-ratiosmallerThan()
 — this is a higher-order function that takes a dimension and returns a photos filter function that returns a sub-collection of photos whose maximum dimensions are smaller than the specified dimension thresholdLet’s say we want to execute this sequence of operations:
The following code snippet shows how we can chain the execution of these operations in a promise sequence:
// Execute asynchronous operations in sequence fetchPhotos() .then(jpegOnly) .then(squareOnly) .then(smallerThan(2500)) .then(listPhotos) .then(console.log) .catch(console.error);
The above code will output a result similar to the image below:
With JavaScript promises, you can execute multiple independent, asynchronous operations in batches or in parallel using the Promise.all()
method.
Promise.all()
accepts an iterable of promises as its argument and returns a promise that is fulfilled when all the promises in the iterable are fulfilled, or is rejected when one of the promises in the iterable is rejected.
If the returned promise fulfills, it is fulfilled with an array of all the values from the fulfilled promises in the iterable (in the same order). However, if it rejects, it is rejected because of the first promise in the iterable that rejected.
Let’s say you are building a weather application that allows users to see the current temperatures of a list of cities they’ve selected.
Using Promise.all()
, you can make a GET
request to the weather API to fetch the temperature of all the selected cities at once, so your users won’t see the data rendering one after the other on your app.
The following code snippet demonstrates how to fetch the current temperatures of the selected cities in parallel with Promise.all()
.
The OpenWeatherMap API service will be used to fetch the weather data, so if you’d like to follow along, head over to their website following that link and sign up to get an API key.
// Use your OpenWeatherMap API KEY // Set the current weather data API URL const API_KEY = 'YOUR_API_KEY_HERE'; const API_URL = `https://api.openweathermap.org/data/2.5/weather?appid=${API_KEY}&units=metric`; // Set the list of cities const CITIES = [ 'London', 'Tokyo', 'Melbourne', 'Vancouver', 'Lagos', 'Berlin', 'Paris', 'Johannesburg', 'Chicago', 'Mumbai', 'Cairo', 'Beijing' ]; const fetchTempForCity = city => { return fetch(`${API_URL}&q=${encodeURIComponent(city)}`) .then(response => response.json()) .then(data => [ city, data.main.temp || null ]); } const fetchTempForCities = cities => { return Promise.all(cities.map(fetchTempForCity)) .then(temps => { return temps.reduce((data, [ city, temp ]) => { return { ...data, [city]: Number.isFinite(temp) ? temp.toFixed(2) * 1 : null }; }, {}); }); } fetchTempForCities(CITIES) .then(console.log, console.error);
In the code block above, we created the functions below:
fetchTempForCity()
 — accepts a single city as its argument and returns a promise that is fulfilled with the current temperature of the given city (in °C) by calling the OpenWeatherMap API service. The returned promise is fulfilled with an array of the format: [city, temperature]
fetchTempForCities()
— accepts an array of cities and fetches the current temperature of each city by leveraging Array.prototype.map()
to call the fetchTempForCity()
function on each city.The Promise.all()
method is used to run the requests in parallel and accumulate their data into a single array, which, in turn, is reduced to an object using an Array.prototype.reduce()
function .
The code snippet above will return an object similar to the result below:
It is important to note that  if any of the fetch temperature promises passed into Promise.all()
are rejected with a reason, the entire promise batch will be rejected immediately with that same reason.
That is, if at least one out of the twelve fetch temperature promises is rejected for some reason, the entire promise batch will be rejected, and hence, no temperature will be returned from the promise.
The scenario described above is usually not the desired behavior  in most cases — a failed temperature fetch should not cause the results of the successful fetches in the batch to be discarded. We can easily fix this by using another promise method promise.allSettled()
, which will be talking about below, but there’s also another simple workaround.
The simple workaround for this is to attach a .catch()
handler to the fetchTempForCity
promise, causing it to fulfill the promise with a null temperature value in cases of rejection.
This is what it will look like:
const fetchTempForCity = city => { return fetch(`${API_URL}&q=${encodeURIComponent(city)}`) .then(response => response.json()) .then(data => [ city, data.main.temp || null ]) // Attach a `.catch()` handler for graceful rejection handling .catch(() => [ city, null ]); }
With that little change to the fetchTempForCity()
function, there is now a very high guarantee that the returned promise will never be rejected in cases where the request fails or something goes wrong. Rather, it will be fulfilled with an array of the format: [city, null]
, like the below:
With this change, it becomes possible to further improve the code to be able to schedule retries for failed temperature fetches.
The following code snippet includes some additions that can be made to the previous code to make this possible.
// An object that will contain the current temperatures of the cities // The keys are the city names, while the values are their current temperatures (in °C) let TEMPS = null; // The maximum number of retries for failed temperature fetches const MAX_TEMP_FETCH_RETRIES = 5; // Fetches the current temperatures of multiple cities (in °C) and update the `TEMPS` object. const fetchTemperatures = (cities, retries = 0) => { return fetchTempForCities(cities) .then(temps => { // Update the `TEMPS` object with updated city temperatures from `temps` TEMPS = (TEMPS === null) ? temps : { ...TEMPS, ...temps }; // Filter the keys (cities) of the `TEMPS` object to get a list of the cities // with `null` temperature values. const RETRY_CITIES = Object.keys(TEMPS) .filter(city => TEMPS[city] == null); // If there are 1 or more cities in the `RETRY_CITIES` list // and the maximum retries has not been exceeded, // attempt to fetch their temperatures again after waiting for 5 seconds. // Also increment `retries` by 1. if (RETRY_CITIES.length > 0 && retries < MAX_TEMP_FETCH_RETRIES) { setTimeout(() => fetchTemperatures(RETRY_CITIES, ++retries), 5 * 1000); } // Return the updated `TEMPS` object return TEMPS; }) .then(console.log, console.error); } // Fetch the current temperatures of the cities in the `CITIES` list // and update the `TEMPS` object fetchTemperatures(CITIES);
In this code snippet, the TEMPS
object is used to hold the updated temperatures of the listed cities. The MAX_TEMP_FETCH_RETRIES
constant is an integer that limits the number of retries that can be done for failed fetches, which is five (5) in this case.
The fetchTemperatures()
function receives an array of city names and the number of retries so far as its arguments. It calls fetchTempForCities()
to fetch the current temperatures for the cities passed to it, and also updates the TEMPS
object with the temperatures.
For failed fetches, the function schedules another call to itself after waiting for five seconds and increments the retries count by 1
. The retries are done for as many times as possible, provided the set maximum has not be exceeded  —  which is five, in our case.
Just as promise.all()
and promise.race()
handle multiple promises, there is another very useful one, promise.allSettled()
, which was added to the JavaScript specification with ES2020.
It is very similar to promise.all()
, but unlike it, promise.allSettled()
isn’t rejected when any of the promises in the iterable passed to it are rejected. Instead, it waits for all promises to be settled (fulfilled or rejected) and then returns an array containing the result of each promise. Let’s see an example below.
const promise1 = Promise.resolve("I got fulfilled!"); const promise2 = Promise.reject("I was rejected!"); Promise.allSettled([promise1, promise2]).then((results) => console.log(results) );
The above code will return the a result like the one below:
[ { status: 'fulfilled', value: 'I got fulfilled!' }, { status: 'rejected', reason: 'I was rejected!' } ]
Now, let’s refactor the OpenWeatherAPI code snippet we wrote above when we discussed promise.all()
, and we implemented a possible workaround for cases where one of the promise is rejected by catching the errors.
With promise.allSettled()
, we don’t need that workaround. It will work just fine and we will also see the rejected promise coupled with the reason. Let’s refactor the code below:
// Use your OpenWeatherMap API KEY // Set the current weather data API URL const API_KEY = "YOUR_API_KEY_HERE"; const API_URL = `https://api.openweathermap.org/data/2.5/weather?appid=${API_KEY}&units=metric`; // Set the list of cities const CITIES = [ "Lagos", "Berlin", "Parwis", // Tweaked this to cause an error ]; const fetchTempForCity = (city) => { return fetch(`${API_URL}&q=${encodeURIComponent(city)}`) .then((response) => response.json()) .then((data) => [city, data.main.temp]); }; const fetchTempForCities = (cities) => { return Promise.allSettled(cities.map(fetchTempForCity)).then((temps) => temps); }; fetchTempForCities(CITIES).then(console.log, console.error);
The result should be like this:
[ { status: "fulfilled", value: ["Lagos", "24.18"]}, { status: "fulfilled", value: ["Berlin", "13.83"]}, { status: "rejected", reason: TypeError: Cannot read properties of undefined(reading 'temp') }, ]
Note: If an empty array is passed to promise.settled()
, it will return a resolved promise with an empty array as the value.
With JavaScript promises, you can race multiple independent asynchronous operations using the Promise.race()
method. Promise.race()
accepts an iterable of promises as its argument and returns a promise that is fulfilled or rejected in the same way as the first settled promise in the iterable.
If the first settled promise in the iterable is fulfilled with a value, the race promise is fulfilled with the same value. However, if it is rejected, the race promise will be rejected with the same reason. If multiple promises are fulfilled or rejected at the same time, then the first promise will be used based on the order of the promises in the iterable.
If the iterable passed to Promise.race()
is empty, then the race promise remains pending forever and is never settled.
Let’s say you are building an API endpoint that does some asynchronous operation, like reading from a file or querying a database, and you want to guarantee that you get a response in 5 seconds , otherwise the request should fail with a HTTP status code of 504
(the Gateway Timeout response).
The following code snippet demonstrates how Promise.race()
can be used to achieve this, assuming we are building the API using the Express.js framework for Node.js.
// Create a new Express app and set the port const app = require('express')(); const PORT = process.env.PORT || 5000; // The timeout in seconds for API responses const TIMEOUT_SECONDS = 5; // Define a new route on the Express app: GET /random app.get('/random', (req, res) => { /** * `execute` is a promise that simulates a time-consuming asynchronous operation * which may take anywhere between 1s - 10s to complete its execution. * On completion, it is fulfilled with an object that looks like this: * { * statusCode: 200, * random: (A random integer in the range of 0 - 100, both inclusive) * duration: (The duration of the execution in seconds, expressed as {duration}s) * } */ const execute = new Promise(resolve => { // Random execution time in milliseconds const timeInMs = Math.floor((Math.random() * 10) * 1000); // Simulate execution delay using setTimeout and fulfill the promise // with the response object setTimeout(() => { resolve({ statusCode: 200, random: Math.floor(Math.random() * 101), duration: `${timeInMs / 1000}s` }) }, timeInMs); }); /** * `requestTimer` is a promise that is settled after `TIMEOUT_SECONDS` seconds * On completion, it is fulfilled with an object that looks like this: * { statusCode: 504 } * which represents a Gateway Timeout on the server. */ const requestTimer = new Promise(resolve => { // Simulate execution delay using setTimeout and fulfill the promise // with the response object const timeoutInMs = TIMEOUT_SECONDS * 1000; setTimeout(() => resolve({ statusCode: 504 }), timeoutInMs); }); /** * `Promise.race()` is used to run both the `execute` and the `requestTimer` promises. * The first of the two promises that gets settled will be used to settle the race promise. * The fulfilled response object is then used to form and send the HTTP response. * If an error occurs, a HTTP 500 error response is sent. */ return Promise.race([ execute, requestTimer ]) .then(({ statusCode = 200, ...data }) => { const response = res.status(statusCode); return (statusCode == 200) ? response.json(data) : response.end(); }) .catch(() => res.status(500).end()); }); // Start the app on the set port app.listen(PORT, () => console.log(`App is running on port ${PORT}.`));
In this code snippet, a very minimalistic Express application has been set up with a single route  — GET/random
for returning a randomly generated integer in the range of 0–100 (both inclusive), while also returning the execution time.
Promise.race()
is used to wait for the first of two promises:
execute
promise that performs some seemingly time-consuming asynchronous operation and gets settled after 1s — 10srequestTimer
promise that does nothing and gets settled after the set TIMEOUT_SECONDS
seconds, which is 5
seconds in this caseSo, here is what happens: whichever of these two promises that is settled first will determine the final response from the endpoint  —  Promise.race()
will make sure of that.
A similar technique can also be used when handling fetch
events in service workers to detect slow networks.
In situations where we want to return the first fulfilled promise, promise.any()
comes in handy. Unlike promise.race()
that returns the first fulfilled or rejected promise, promise.any()
returns the first fulfilled promise. If no promise is fulfilled, it will return a rejected promise with an AggregateError
object.
Of the four promise methods, (any(), race(), allSettled()
, and all()
, only promise.allSettled()
executes all the promises passed to it because it waits for all promises to be resolved. The others do not, and so they are said to have a short circuit.
Below is a table from a GitHub gist created by Sung M. Kim showing the differences between promise.all()
, promise.allSettled()
, promise.race()
, and promise.any()
.
Async
and await
are keywords that can make writing promises cleaner. Preceding a function with the keyword async
will make the function return a promise, which then permits it an await
keyword inside. With await
, we can wait for the promise to be resolved. With async
and await
, we don’t need to use .then()
. Let’s see an example below using the OpenWeather API example once more:
// Set the current weather data API URL const API_KEY = "YOUR_API_KEY"; const API_URL = `https://api.openweathermap.org/data/2.5/weather?appid=${API_KEY}&units=metric`; const fetchTempForCity = async (city) => { let response = await fetch(`${API_URL}&q=${encodeURIComponent(city)}`); response = await response.json(); console.log(response) }; fetchTempForCity('Port harcourt');
We can handle possible errors with try…catch
method. If the promise is rejected, we will get the error in the catch
block.
const fetchTempForCity = async (city) => { try { let response = await fetch(`${API_URL}&q=${encodeURIComponent(city)}`); response = await response.json(); console.log(response); } catch (error) { console.error(error.message) } }; fetchTempForCity('Port harcourt');
JavaScript promises can drastically change the way you go about writing asynchronous programs, making your code more succinct and clearer in regard to the desired intent.
In this guide, we looked at several ways promises can be used in asynchronous programs, like:
We also saw how to use async/await functions and await
keyword handle promises. You can learn more about promises from MDN’s JavaScript promise docs.
Debugging code is always a tedious task. But the more you understand your errors, the easier it is to fix them.
LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to see exactly what the user did that led to an error.
LogRocket records console logs, page load times, stack traces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]