Announcing Bito’s free open-source sponsorship program. Apply now

Get high quality AI code reviews

Call URLs from JavaScript

Table of Contents

Interacting with URLs is a fundamental part of web development. Whether you need to fetch data from an API, submit a form, or dynamically generate links, JavaScript provides powerful tools for invoking URLs and handling responses right from the browser.

In this comprehensive guide, we’ll explore the most common techniques for calling URLs using vanilla JavaScript as well as popular libraries like jQuery. By the end, you’ll have a solid understanding of making HTTP requests, executing client-side routines, and working with asynchronous responses.

Making HTTP Requests

Sending HTTP requests allows you to interact with web servers programmatically. JavaScript enables developers to make GET, POST, PUT, and DELETE requests to load or submit data without requiring a page refresh.

There are a few different interfaces available for making HTTP calls from JavaScript:

The Fetch API

The Fetch API provides a modern, promise-based way to make network requests. It is supported in all major browsers and provides a simple syntax for fetching resources asynchronously:

fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error(error))

To make a GET request, simply invoke fetch() with the URL as the parameter. It returns a promise containing the response object.

We use .json() to parse the response body as JSON and access the data. If an error occurs, it’s caught and logged in the .catch() handler.

Other advantages of the Fetch API include:

  • Easily make POST, PUT, and DELETE requests by passing a config object as the second parameter
  • Handle various response types like JSON, text, blob, arrayBuffer, etc.
  • Configure headers, body, cache mode, credentials, and other options
  • Abort ongoing fetches using the AbortController interface

Overall, Fetch provides a cleaner alternative to older HTTP interfaces like XMLHttpRequest.

XMLHttpRequest

The XMLHttpRequest API, commonly abbreviated as XHR, has been available for many years and is supported by all browsers. It provides more low-level control over making HTTP requests:

const xhr = new XMLHttpRequest();

xhr.open('GET', 'https://api.example.com/data');

xhr.onreadystatechange = function() {
  if(xhr.readyState === 4 && xhr.status === 200) {
    console.log(xhr.responseText);
  }
}

xhr.send(); 

Here’s what happens in the code above:

  • Initialize a new XMLHttpRequest object
  • Open a connection with the open() method, specifying the HTTP method and URL
  • Attach an onreadystatechange callback that executes whenever the state changes
  • Send the request with the send() method
  • Inside the callback, check readyState and status to confirm successful response
  • Access responseText for the response data

The XMLHttpRequest API also allows:

  • Sending request body data with POST, PUT, etc.
  • Setting custom headers with the setRequestHeader() method
  • Configuring timeouts, authentication, and other advanced options

While it requires more code, XHR allows finer control compared to the Fetch API.

jQuery AJAX

The jQuery library simplifies HTTP requests with its AJAX methods. They provide shorthand ways to execute common calls without dealing with low-level details:

$.get('https://api.example.com/data')
  .done(data => console.log(data)) 
  .fail(error => console.error(error))

$.get() executes a GET request, while $.post() can be used for POSTs. The .done() and .fail() callbacks handle the response.

jQuery also provides the more advanced $.ajax() method:

$.ajax({
  url: 'https://api.example.com/data',
  type: 'POST',
  data: {key: 'value'},
  contentType: 'application/json' 
})
  .done(data => console.log(data))
  .fail(error => console.error(error))

This allows configuring options like the URL, HTTP method, headers, and body while abstracting away the nuts and bolts.

Benefits of using jQuery for AJAX include:

  • Cross-browser support and consistency
  • Shorthand methods require less code than raw XHR
  • Easily configure and send all HTTP request types
  • Manage callbacks, errors, and processing in a standard way
  • Support for promises in recent versions

If you plan to use jQuery in your project, its AJAX methods can spare you from working directly with XMLHttpRequest.

Executing Client-Side Routines

In addition to sending standard HTTP requests, you can also use JavaScript to execute server-side routines by dynamically manipulating URLs in the browser.

window.location

The window.location object represents the current URL and exposes properties like hostname, pathname, search, and href that can be read and modified.

You can change the page URL programmatically by assigning a new value to window.location.href:

// Navigate to new URL
window.location.href = '/new-page'; 

// Appends query string parameter
window.location.href = window.location.href + '?id=200';

This immediately redirects the browser to the new URL, triggering a full page refresh.

The benefit of using window.location is you can construct URLs that execute server-side code while remaining client-side. For example, redirecting to a URL that deletes a record or triggers an action.

The limitation is it always causes a hard redirect rather than fetching data asynchronously.

Hidden Forms

Submitting a form allows executing server-side logic without leaving the current page. By creating a hidden form and using JavaScript to submit it, you can call URLs in the background:

<form id="hidden-form" method="POST" action="/submit-data" target="_blank">
  <input type="hidden" name="key" value="value">
</form>

<script>
  document.getElementById('hidden-form').submit(); 
</script>

Here we create a form with hidden inputs to pass data, and use target="_blank" so the results load in a new tab rather than replacing the current page.

Then JavaScript programmatically submits the form, which sends a POST request to the specified action URL. This allows calling server-side logic without reloading the original page.

iframes

An iframe allows embedding another HTML document within the current page. By dynamically generating iframes, you can load the response of arbitrary URLs into your application:

// Create iframe 
const iframe = document.createElement('iframe');

// Set source to URL 
iframe.src = 'https://api.example.com/data';

// Append to DOM 
document.body.appendChild(iframe);

// Response will load in iframe 

Here we create and configure an iframe element then add it to the document, causing the URL to be requested in the background. The response will be displayed within the iframe.

You can even access the content using properties like iframe.contentDocument and iframe.contentWindow for further JavaScript manipulation.

The benefit of iframes is being able to fetch and render content in the background without affecting the host page. The downside is they create separate document environments.

Handling Asynchronous Responses

The requests we’ve looked at so far have all been sent asynchronously. This means JavaScript execution continues without waiting for the response. So how do we run code after an async operation finishes?

There are a few patterns for handling asynchronous responses in JavaScript:

Callback Functions

Callbacks provide a traditional way to specify logic that executes when an asynchronous request completes.

For example, when using the Fetch API:

fetch('/data')
  .then(response => {
    // Runs on success
  })
  .catch(error => {   
    // Runs on error
  })

We pass callback functions to .then() and .catch() that run after the promise resolves or rejects.

Callbacks work similarly with XMLHttpRequest:

xhr.onreadystatechange = function() {
  // Runs whenever state changes
}

And jQuery AJAX:

$.get('/data')
.done(data => {
// Success callback
})
.fail(error => {
// Failure callback
})

No matter the HTTP interface, callbacks allow executing code to handle the response after an asynchronous operation completes.

Promises

Promises provide an alternative to callbacks for dealing with async actions. A Promise object represents an operation that hasn’t completed yet but is expected in the future.

We’ve already seen the Fetch API returns promises:

const promise = fetch('/data');

promise.then(response => {
// Handle response
})

The .then() registered on the promise runs when the HTTP call resolves.

Some benefits of promises include:

  • Avoid callback hell with chained .then()s
  • Handle all errors in .catch() instead of each callback
  • Support for async/await syntax
  • Common interface across APIs

Promises allow writing more sequential, synchronous-looking code for async operations.

Async/Await

Async/await builds on promises and provides an even cleaner syntax. By declaring an async function, you can use the await keyword within it:

async function fetchData() {
const response = await fetch('/data');
const data = await response.json();
return data;
}

await pauses execution until a promise resolves, allowing you to write asynchronous code imperatively without nesting callbacks.

Under the hood, async/await uses promises. The await expression evaluates to the resolved promise result.

Advantages include:

  • Write asynchronous code that reads like synchronous JavaScript
  • Implicitly handle promises with await instead of .then() chains
  • Simpler control flow with standard try/catch error handling

Async/await requires less boilerplate than promises for many async workflows.

Putting It All Together

We’ve covered a lot of ground looking at techniques for calling URLs and handling responses. Let’s review some best practices and recommendations:

  • Use the Fetch API or jQuery AJAX for most HTTP requests – they provide simple interfaces that work consistently across browsers.
  • Fall back to XMLHttpRequest for legacy browser support, or when you need low-level control over the request lifecycle.
  • Use async/await whenever possible to write asynchronous JavaScript code without nested callbacks.
  • Leverage window.location and hidden forms for executing server-side routines from client-side code when necessary.
  • Choose Wisely between callbacks, promises, and async/await based on the browser support and use case.
  • Remember iframes as an option for embedding external content, but understand their limitations.

Here is an example putting together some of these techniques:

// Async function to fetch data
async function getData() {
// Use try/catch with await
try {
const response = await fetch('/data');
return await response.json();
} catch (error) {
console.error('Error fetching data', error);
}
}
// On button click, call API
document.getElementById('button').addEventListener('click', () => {
// Execute async function
getData()
.then(data => {
// Handle success
})
.catch(error => {
// Handle error
});
});

This demonstrates a clean pattern for an asynchronous workflow – declare an async helper function, use try/catch to handle errors, and execute the function on demand while handling the promise result.Following best practices like this will ensure your code interacts with URLs and handles responses reliably.

Conclusion

This guide provided a comprehensive overview of techniques for calling URLs and handling responses in JavaScript:

  • The Fetch API, XMLHttpRequest, and jQuery AJAX allow making diverse HTTP requests like GET, POST, and more from the browser.
  • Tools like window.location, hidden forms, and iframes enable triggering server-side routines without reloading the page.
  • Callbacks, promises, and async/await provide patterns for executing code after asynchronous actions complete.

By choosing the right approach for your application’s browser support, architecture, and use cases, you can build complex interactions between client and server.Calling URLs is fundamental to dynamic web apps, so mastering these JavaScript techniques opens the door to all kinds of possibilities. You can fetch data from APIs to feed user interfaces, submit forms without refreshing, and offload processing to the server.Understanding the tools covered here paves the way for you to create more powerful front end applications.

Picture of Nisha Kumari

Nisha Kumari

Nisha Kumari, a Founding Engineer at Bito, brings a comprehensive background in software engineering, specializing in Java/J2EE, PHP, HTML, CSS, JavaScript, and web development. Her career highlights include significant roles at Accenture, where she led end-to-end project deliveries and application maintenance, and at PubMatic, where she honed her skills in online advertising and optimization. Nisha's expertise spans across SAP HANA development, project management, and technical specification, making her a versatile and skilled contributor to the tech industry.

Written by developers for developers

This article was handcrafted with by the Bito team.

Latest posts

Mastering Python’s writelines() Function for Efficient File Writing | A Comprehensive Guide

Understanding the Difference Between == and === in JavaScript – A Comprehensive Guide

Compare Two Strings in JavaScript: A Detailed Guide for Efficient String Comparison

Exploring the Distinctions: == vs equals() in Java Programming

Understanding Matplotlib Inline in Python: A Comprehensive Guide for Visualizations

Top posts

Mastering Python’s writelines() Function for Efficient File Writing | A Comprehensive Guide

Understanding the Difference Between == and === in JavaScript – A Comprehensive Guide

Compare Two Strings in JavaScript: A Detailed Guide for Efficient String Comparison

Exploring the Distinctions: == vs equals() in Java Programming

Understanding Matplotlib Inline in Python: A Comprehensive Guide for Visualizations

Get Bito for IDE of your choice