DEV Community

Cover image for NgSysV2-5.1: The Javascript "Fetch" function and the "Await" keyword - for Svelte users
MartinJ
MartinJ

Posted on • Edited on

NgSysV2-5.1: The Javascript "Fetch" function and the "Await" keyword - for Svelte users

This post series is indexed at NgateSystems.com. You'll find a super-useful keyword search facility there too.

Last reviewed: Dec '24

1. Introduction

The JavaScript "Fetch" function provides the software equivalent of your favourite hammer when you need to build something that calls a web address (conventionally referred to as an "endpoint") to:

  • run an internal operation such as a routine data reorganisation, or
  • despatch some data for processing by a third-party site- for example, a message to be circulated by a mailing site, or
  • receive some data from a third party such as a weather-forecasting site.

When you're building a webapp with Svelte, you'll find you need "Fetch" on far fewer occasions than you would have in the past. This is because Svelte's built-in support for server-side operations through its load() and actions() integrations replaces those occasions when you would have had to make "Fetch" calls to access bits of your own site. Nevertheless, when you need it, you need it and "Fetch" will be fabulously useful to you.

2. The Asynchronous Javascript "Fetch" function

As mentioned briefly in Post 2.3, Javascript is an asynchronous programming language, and the "Fetch" function is exactly the sort of activity that triggers this behaviour. In such cases, the flow of activity from statement to statement doesn't halt when it encounters a statement that will take an indeterminate length of time to complete. Rather, it simply sidelines the statement into a parallel thread and ploughs on, assuming that the thread will register an "event" that will fire on completion and trigger an appropriate conclusion. I suggest that you might like, at this point, to ask chatGPT to give you a quick tutorial on the Javascript Event Loop!

As you may imagine, all of this can complicate a coder's life, so you won't be too surprised to learn that this aspect of the language has experienced intense development. I won't go into the history of all this as this would only confuse you. What I describe below simply presents the current state of play.

For most purposes, probably the most useful form of the "Fetch" function is one that uses the await keyword to locally "conceal" Javascript's asynchronous behaviour while it performs a request that sends some data to a remote address. In return, it receives, in another awaited statement, a response that returns the requested information. Each of these steps appears to behave synchronously and thus permits you to submit a series of such requests in strict sequence. Here's a "template" for such a "Fetch" application using the "POST" method:

    async function performMyPostTask() {
        try {
            const response = await fetch(
                urlForPost,
                {
                    method: "POST", // HTTP method
                    headers: {
                        "Content-Type": "application/json",
                    },
                    body: JSON.stringify({
                        title: "foo",
                        body: "bar",
                        userId: 1,
                    }),
                },
            );
            if (!response.ok) {
                throw new Error("HTTP error! Status: " + response.status);
            }
            const data = await response.json();
            return data;
        } catch (error) {
            console.error("Error:", error);
        }
    }
Enter fullscreen mode Exit fullscreen mode

Now, where do I begin explaining this? Let's start with the await and async keywords.

3. The Javascript await and async keywords

ECMA, the organisation responsible for the JavaScript standard, added the await keyword to the language in 2017. Essentially, this makes a function call wait for a "promise" to "resolve" before proceeding to the next statement. The same ECMA standard also introduced an async keyword that you use when declaring a function that uses await to make it return a "promise".

Many programmers initially found these concepts challenging, but they’ve become a standard part of modern JavaScript workflows. Once you're used to the arrangement, you'll find yourself automatically using long sequences of await calls as if each was a synchronous operation.

Just to put this into context then, the "Fetch" example above would typically be encountered in a button function such as the following example:

<button
    on:click={async () => {
        const data = await performMyPostTask();
        console.log("data: " + JSON.stringify(data));
    }}>Click Me</button
>
Enter fullscreen mode Exit fullscreen mode

Of course, there will be times when you positively want to take advantage of Javascript's asynchronous behaviour and launch a "battery" of database calls that run in parallel. This can make a huge difference to the responsiveness of your webapp. Each call can be coded to return a promise that you tuck into an array. In turn, this can be monitored by a global function "awaiting" for them all to resolve. You can see an example of this arrangement in the "Reading Documents" section of Post 10.1.

An alternative to the async and await keywords is to use a .then method subsequent to the call of an asynchronous function. This gives you more control of error handling arrangements but can become cumbersome when used to code long sequences of asynchronous operations. Personally, I've always preferred the await method

The next thing you'll want to know is what this "POST" business is all about.

4. "Fetch" methods - "GET" and "POST"

A "Fetch" function's "Method" declares the mechanism that will be used to transfer data to the target endpoint. It's probably best to start with the arrangement used by the "GET" method. This arrangement is built into the browser so you can use a"GET" call without actually writing any Javascript. Here, the data being sent to the endpoint is coded directly into the URL used to "call" the endpoint. Here's an example (coded as a call to a demonstrator GET endpoint configured as a +server.js file in the local API folder):

http://localhost:5173/api/perform-my-get-task?taskSpec=taskSpecValue
Enter fullscreen mode Exit fullscreen mode

Here's how you might use fetch() to code this programmatically:

<script>
    const urlForGet = "http://localhost:5173/api/perform-my-get-task";
    const taskSpec = "taskSpecValue";

    async function performMyGetTask() {
        try {
            const response = await fetch(urlForGet + "?taskSpec=" + taskSpec);
            if (!response.ok) {
                throw new Error("HTTP error! Status: " + response.status);
            }
            const data = await response.json(); // Parse JSON response
            console.log("Task result:", data);
        } catch (error) {
            console.error("Error during fetch:", error);
        }
    }

    callPerformMyGetTask();
</script>
Enter fullscreen mode Exit fullscreen mode

Here's what the perform-my-get-task endpoint might look like:

// src/routes/api/perform-my-get-task/+server.js
import { json, error } from '@sveltejs/kit';

export function GET({ url }) {
    // Get the `taskSpec` query parameter
    const taskSpec = url.searchParams.get('taskSpec');

    if (!taskSpec) {
        // Return a 400 error if the parameter is missing
        throw error(400, { message: 'Missing taskSpec query parameter' });
    }

    // Simulate performing the task
    console.log("Task received with spec: " + taskSpec);
    const result = {
        message: "Task " + taskSpec + " has been successfully performed",
        timestamp: new Date().toISOString(),
    };

    // Return the result as a JSON response
    return json(result);
}
Enter fullscreen mode Exit fullscreen mode

The functional "payload" of the +server.js file is "exported" here because SvelteKit requires server routes to expose specific handler functions (GET, POST, etc.) for incoming HTTP requests.

A little thought will awaken you to the limitations of GET - the parameters must be coded into a string, browsers impose a maximum length on the endpoint call string and the string is visible in the browser's URL field.

By contrast, the POST method minimises most of these limitations. Here, data is passed as an object, can be substantially larger and is no longer visible in the URL (though still not strictly secure).

Here's what a perform-my-post-task endpoint might look like:

// src/routes/api/perform-my-post-task/+server.js
import { json, error } from '@sveltejs/kit';

export async function POST({ request }) {
    try {
        // Parse the JSON body from the incoming request
        const body = await request.json();

        // Log the received data for debugging purposes
        console.log("Received body:", body);

        // Simulate a response payload
        const simulatedResponse = {
            id: 101, // Simulated ID for the created resource
            ...body, // Echo back the received data
            createdAt: new Date().toISOString(), // Add a timestamp for when the resource was created
        };

        // Return the response as JSON with a 200 OK status
        return new Response(JSON.stringify(simulatedResponse), {
            status: 200,
            headers: { "Content-Type": "application/json" },
        });
    } catch (error) {
        console.error("Error processing POST request:", error);

        // Return a 500 Internal Server Error for unexpected issues
        return new Response(
            JSON.stringify({ error: "Internal Server Error" }),
            { status: 500, headers: { "Content-Type": "application/json" } }
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

5. "Fetch" in Svelte

The "Fetch" API only became available in server-side code in 2022. This was because server-side code runs in Node.js (as opposed to the JavaScript environment provided by web browsers), and early versions of Node.js didn't natively provide a "Fetch" function.

But even before that date, Svelte developers were insulated from the consequent problems because SvelteKit provided its own "polyfill" replacement. This meant that you could code server-side "Fetch" calls in exactly the same way as for client-side code.

This was a big help because the fall-back alternatives in Node.js equivalents of "Fetch" were third-party libraries like Axios or node-fetch. These were fine in themselves (and, indeed, Axios offers some useful features like automatic JSON parsing), but they required you to learn a different calling pattern (groan). SvelteKit's polyfill was a big help because it meant that you could use a consistent "Fetch" interface regardless of whether your code was running on the client or the server.

Even now that "Fetch" is formally available in Node.js, SvelteKit continues to double down on its original approach and provides a special version of "Fetch" that :

  • Supports relative URLs, allowing you to make same-origin requests without needing fully qualified URLs.
  • Automatically includes cookies from the incoming client request when making relative, same-origin requests. This makes it easy to provide session-based authentication.

Note that, for cross-origin requests, you’ll still need to manually manage headers like Authorization.

Top comments (0)