Why is `setTimeout(resolve)` necessary in my JavaScript promise for loading data?

Instead of simply copying example code from tutorials, I am challenging myself to grasp ES6 Promises by implementing them in a meaningful way within a real project.

Below is the frontend Vue.js/axios code I created that effectively utilizes Promises to populate five sections of the screen with data. In my backend loadData action, I intentionally insert a one-second delay so I can observe each section loading sequentially:

<div id="app" class="pageContent">
    <div>Data 1: {{data1}}</div>
    <div>Data 2: {{data2}}</div>
    <div>Data 3: {{data3}}</div>
    <div>Data 4: {{data4}}</div>
    <div>Data 5: {{data5}}</div>
</div>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            data1: 'loading...',
            data2: 'loading...',
            data3: 'loading...',
            data4: 'loading...',
            data5: 'loading...'
        },
        methods: {
            initialize: function () {
                const that = this;
                this.loadData('data1')
                    .then(() => {
                        return that.loadData('data2');
                    })
                    .then(() => {
                        return that.loadData('data3');
                    })
                    .then(() => {
                        return that.loadData('data4');
                    })
                    .then(() => {
                        return that.loadData('data5');
                    });
            },
            loadData: function (idCode) {
                return new Promise((resolve, reject) => {
                    const that = this;
                    axios({
                        method: 'post',
                        url: 'controllerShowcaseLoadDataWithPromises',
                        data: {
                            action: 'loadData',
                            idCode: idCode
                        }
                    }).then(function (responseObject) {
                        const response = qsys.getResponse(responseObject);
                        that[idCode] = response.data.message;
                        setTimeout(resolve); // This line is crucial for proper execution
                    });
                });
            },
        }
    });
    app.initialize();

Although the code functions as expected, I am puzzled about the necessity of using setTimeout(resolve). Could someone shed light on why this is needed and provide insight into a cleaner, more standard approach to achieve the same result?

Answer №1

...why do I need to utilize setTimeout(resolve)

Using setTimeout is not necessary, but it is essential for you to invoke resolve (or reject) in order to resolve the promise.

what exactly happens in this scenario?

You are performing a sequence of asynchronous calls where each subsequent call waits for the previous one to finish due to how the promises are chained together. This explains why if you fail to resolve the first call, none of the following ones will execute. By chaining your operations in this manner, each subsequent operation only proceeds if the preceding one was successful. Failing to call resolve means that although the operation completed, the created promise remains unresolved, failing to notify the code utilizing the promise that the task has been completed.

I realize that this is similar to executing a callback as done in the past before promises, but what is a cleaner, more conventional approach to handle this?

The standard method involves calling resolve when explicitly creating a promise. However, in your case, since you already have a promise from axios, there's no need to create an additional promise using loadData:

loadData: function(idCode) {
    return axios({
        method: 'post',
        url: 'controllerShowcaseLoadDataWithPromises',
        data: {
            action: 'loadData',
            idCode: idCode
        }
    }).then(function(responseObject) {
        const response = qsys.getResponse(responseObject);
        this[idCode] = response.data.message;
    });
},

Notice that it simply returns the result of calling then on the existing axios promise. Additionally, with arrow functions, there's no need for declaring that = this because arrow functions inherently capture the context of this.

To sequentially process the five operations, you can streamline your promise chain while handling errors appropriately:

this.loadData('data1')
    .then(() => this.loadData('data2'))
    .then(() => this.loadData('data3'))
    .then(() => this.loadData('data4'))
    .then(() => this.loadData('data5'))
    .catch(error => {
        // Handle/report error here
    });

In cases where you want all operations to run concurrently, you can initiate them simultaneously and await their completion using Promise.all:

Promise.all([
    this.loadData('data1'),
    this.loadData('data2'),
    this.loadData('data3'),
    this.loadData('data4'),
    this.loadData('data5'),
])
.catch(error => {
    // Handle/report error here
});

For modern browsers or with the use of transpilation, you can also leverage async functions and await for more streamlined asynchronous handling:

loadData: async function(idCode) {
    const responseObject = await axios({
        method: 'post',
        url: 'controllerShowcaseLoadDataWithPromises',
        data: {
            action: 'loadData',
            idCode: idCode
        }
    });
    const response = qsys.getResponse(responseObject);
    this[idCode] = response.data.message;
},

Answer №2

If you want a more organized approach to what you're currently doing, consider storing all the promises in an array and using Promise.all:

const requests = [
    that.loadData('data1'),
    that.loadData('data2'),
    that.loadData('data3'),
    that.loadData('data4'),
    that.loadData('data5')
];

Promise.all(requests).then(value => {
    // All resolved values will be accessible in order
}).catch(error => {
    // Error handling here
});

The reason why your loadData function is only running once when you omit the resolve call is because you're not resolving the promise. As a result, the then block is never executed.

Remember, there's no need to use setTimeout to wrap around resolve.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the process of implementing filters and search functionality in Vue.js tables?

I'm currently delving into Vue.js while constructing a Single Page Application. I am curious about how to integrate filters and search functionality using an input tag. Additionally, I'm looking to implement a feature where clicking on a name in ...

Looking for a JavaScript library to display 3D models

I am looking for a JavaScript library that can create 3D geometric shapes and display them within a div. Ideally, I would like the ability to export the shapes as jpg files or similar. Take a look at this example of a 3D cube: 3d cube ...

Instead of uploading multiple files at once, allow users to upload individual files by clicking on the table row

In my dynamic table, there are 4 columns. The first 3 columns in each row have an input type='file' where users can choose a file to upload, and the 4th column has a submit button aligned with the rows. While submitting files in the first row wor ...

Steps for disabling and collapsing an individual header on the JQuery Accordian

Looking to adjust the behavior of 4 headers in accordions? Specifically, you want to collapse and disable only the first header out of the set. Here's how: $("#ExpandCollapse").accordion({ active: false, collapsible: true }); To ...

Resetting the Angular provider configuration whenever the service is injected into a different location

Trying to wrap my head around a rather complex issue here. I have a service set up as a provider in order to configure it. Initially, this service has an empty array of APIs which can be dynamically added to by various configuration blocks. When adding API ...

Guide on populating a dropdown menu dynamically in php based on the selection made in another dropdown

Despite reviewing similar questions, I have not found a solution to my problem. I am attempting to retrieve a dropdown menu based on the selection of another dropdown. The first dropdown consists of school names, and upon selection, it should fetch the use ...

Which directives in AngularJS facilitate one-way binding?

Which directives in AngularJS support one-way binding? While ng-model enables two-way binding, what about ng-bind and {{ }} expressions - do they also support one-way binding? ...

JavaScript Object Instantiation within the Object Prototype

If we consider a scenario where there are 3 chapters of a novel each with its own unique URL structure: Chapter 1 = /1.html Chapter 2 = /2.html Chapter 3 = /3.html Now, to implement object-oriented principles using jQuery, the idea is to create two JS ...

A server-side rendered page in Next.js that functions without the need

Hey everyone, I'm curious about the best way to serve an HTML page in a Next Js application without relying on additional JavaScript. I need this because I want to make sure my webpage can be accessed by users who have older phones like Symbian or oth ...

When trying to load AJAX, it does not function properly unless the page is refreshed

I'm currently working on a web page and encountering some difficulties. Within my HTML, I have two divs that are refreshed using AJAX. The issue is that the content loading seems to be inconsistent unless I manually refresh the page or implement a tim ...

Having trouble accessing the div element inserted by the Angular application

I've encountered an issue while trying to access a div that is injected into the DOM by an Angular app on the page. Below is the script I have placed at the end of the HTML page: $(document).ready(function () { var targetNode = document.querySelect ...

Guide to creating a Discord bot that replies privately to users without other channel members being able to see the messages

As a newcomer to Discord, I am attempting to create a bot that will respond to user commands by sending a reply that only the user who issued the command can see. However, I am struggling to figure out how to implement this feature. Below is the source c ...

Please eliminate the forward slash (/) from the end of my route

Could anyone help me figure out how to remove the trailing slash at the end of routes in Nuxtjs? I attempted using the @nuxtjs redirect-module and setting the trailingSlash property to false, but instead of removing the slash, it adds multiple slashes at ...

Methods of using jQuery to conceal table rows according to child class name

I need to filter out rows in a table that do not contain a specific class. For instance: <tr id="game-22590" class="game"> <td class="pos left-edge"> <div>2</div> </td> <td class="cost"> < ...

The Grid within the Container is presented vertically rather than horizontally

I followed the coding style exactly as shown in a recent tutorial on YouTube, where the cards were displayed in a row. However, when I implemented it, they are appearing strangely in a column. Why is this happening? The default should be inline. Even afte ...

Error message: "Vue-i18n is not defined in the vuex

I've been attempting to update the locale in my Vue project using Vuex and VueI18n. Initially, I thought I had figured out a way to update the locale by setting the i18n value in the store object and then using it like this (mutation): UPDATE_LAN ...

What is the best way to pass a value to a modal and access it within the modal's component in Angular 8?

How can I trigger the quickViewModal to open and send an ID to be read in the modal component? Seeking assistance from anyone who can help. Below is the HTML code where the modal is being called: <div class="icon swipe-to-top" data-toggle="modal" da ...

Error: The integer provided in the Stripe payment form is invalid in the

I'm encountering an error that I can't seem to figure out. I believe I'm following the documentation correctly, but Stripe is not able to recognize the value. Have I overlooked something important here? https://stripe.com/docs/api/payment_i ...

The button colors in Material Ui do not update properly after switching themes

React Material UI is being utilized in this project. Although the theme is being overridden, the colors of buttons and inputs remain unchanged. Here is how the Material UI theme is created in theme.js // @flow import { createMuiTheme } from '@materi ...

Checking the validity of subdomain names using JavaScript/jQuery

For users signing up, my form allows them to choose a subdomain for their account. The valid characters allowed in the subdomain are letters, numbers, and dashes. Spaces and most special characters should be filtered out. http://_________.ourapp.com To r ...